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

. Contexto

Los beans de entidad no se han pensado para representar todos los objetos persistentes del modelo. Los beans de entidad son mejores para objetos de negocio persistentes genéricos.

. Problema

En una aplicación de la plataforma J2EE, los clientes -- como aplicaciones, páginas JSP, servlets, componentes JavaBeans -- acceden a los beans de entidad mediante sus interfaces remotos. Así, toda llamada de cliente potencialmente se enruta a través de los stubs (trozos) y skeletons (esqueletos), incluso si el cliente y el bean enterprise están en la misma Máquina Virtual Java, en el mismo sistema operativo o en la misma máquina física. Cuando los beans enterprise son objetos específicos, los clientes tienden a invocar los métodosdel bean de forma más individual, resultando en una gran sobrecarga en la red.

Los beans de entidad representan objetos persistentes de negocio distribuidos. Siempre que desarrollemos o migremos una aplicación a la plataforma J2EE, la especificad de los objetos es muy importante cuando decidimos implementarlos como beans de entidad. Los beans de entidad deberían representar objetos de negocio genéricos, como aquellos que proporcionan un comportamiento complejo más allá de simplemente obtener y seleccionar valores de campos. Estos objetos genéricos normalmente tienen objetos dependientes. Un objeto dependiente es un objeto que no tiene un significado real cuando no está asociado con su padre genérico.

Un problema recurrente es el mapeo directo del modelo a un modelo de JavaBeans Enterpise (especificamente beans de entidad). Esto crea una relación entre los objetos beans de entidad en su consideración de genéricos contra objetos específicos (o dependientes). Determinar cuando utilizar objetos genéricos o especificados es muy dificil y se puede hacer mejor utilizando un modelo de relaciones mediante Unified Modeling Language (UML).

Hay varias áreas que se ven impactadas por la aproximación del diseño del bean de entidad específico:

  • Relaciones de Entidades - Mapear directamente un objeto de modelo a un modelo EJB no tiene en cuenta el impacto de las relaciones entre objetos. Las relaciones inter-objetos se transforman directamente en relaciones inter-beans. Como resultado, un bean de entidad podría contener o almacenar una referencia remota a otro bean de entidad. Sin embargo, mantener referencias remotas a objetos distribuidos implica técnicas y semánticas diferentes a la de mantener referencias a objetos locales. Junto con el incremento de complejidad del código, se reduce la flexibilidad porque el bean de entidad debe cambiar si cambia alguna de sus relaciones.
    Además, no hay garantías de la validez de las referencias de un bean de entidad a otro bean de entidad en el tiempo. Dichas referencias se establecen dinámicamente utilizando el objeto home de la entidad y la clave primaria para ese ejemplar de bean. Esto implica una alta sobrecarga de mantenimiento para el chequeo de validez de la referencia por cada referencia de bean-de-entidad-a-bean-de-entidad.
  • Manejabilidad - Implementar objetos específicos como beans de entidad resulta en un mayor número de beans de entidad en el sistema, el desarrollador debe proporcionar clases para el interface home, el interface remote, la implementación del bean y la clave primaria.
    Además, el contenedor podría generar clases para soportar la implementación del bean de entidad. Cuando se crea el bean, esta clases se convierten en objetos reales en el contenedor. En breve, el contenedor crea varios objetos para soportar cada ejemplar de bean de entidad. Un mayor número de beans de entidad significa más clases y código que mantener por el equipo de desarrollo. También resulta en un mayor número de objetos en el contenedor. Esto puede impactar negativamente en el rendimiento de la aplicación.
  • Rendimiento de Red -- los beans de entidad específicos, potencialmente tienen más relaciones inter-beans. Los beans de entidad son objetos distribuidos. Cuando un bean de entidad invoca un método de otro bean de entidad, el contenedor trata esa llamada como una llamada remota, incluso si ambos beans están en el mismo contenedor. Si el número de relaciones bean-de-entidad-a-bean-de-entidad se incrementa, se reduce la escalabilidad del sistema debido a la fuerte sobrecarga de la red.
  • Dependencia del Esquema de Base de Datos - Cuando los beans de entidad son específicos cada ejemplar normalmente representa una sóla fila de la base de datos. Esto no es una aplicación apropiada del diseño de beans de entidad, ya que los beans de entidad son más apropiados para componentes genéricos. La implementación de beans de entidad específicos normalmente es una representación directa del esquema de la base de datos subyacente en el diseño del bean de entidad. Cuando los clientes utilizan estos beans de entidad específicos, están operando esencialmente a nivel de fila en la base de datos, ya que cada bean de entidad efectivamente es una única fila. Como el bean de entidad modela directamente una sola fila de la base de datos, los clientes dependen del esquema de la base de datos. Cuando el esquema cambia, también deben cambiar las definiciones del bean de entidad. Además, como los clientes están operando con la misma especificidad, deben observar y reaccionar ante estos cambios. Esta dependencia del esquema causa una pérdida de flexibilidad e incrementa la sobrecarga de mentenimiento requerida cuando cambia el esquema.
  • Especificidad del Objeto (Genérico frente a Específico) -- La especificidad del objeto impacta en la transferencia de datos entre el bean enterprise y el cliente. En muchas aplicaciones, los clientes necesitan un trozo de datos mayor que una o dos filas de la tabla. En dichos casos, implementar cada unos de estos objetos específicos como un bean de entidad significa que el cliente tendría que manejar las relaciones entre todos esos objetos específicos. Dependiendo de los requerimientos de datos, el cliente podría tener que realizar muchas búsquedas de varios beans de entidad para obtener la información necesaria.

. Causas

  • Los beans de entidad son mejores para implementar objetos genéricos debido a la alta sobrecarga asociada con todo bean de entidad. Todo bean de entidad se implementa utilizando muchos objetos, como el objeto home, el objetoremote, la implementación del bean, y la clave primaria, y todos son controlados por los servicios del contenedor.
  • Las aplicaciones que mapean directamente un esquema de una base de datos relacional a beans de entidad (donde cada fila de la tabla se representa como un ejemplar de un bean de entidad) tienden a tener un mayor número de beans de entidad específicos. Es deseable mantener el número de beans de entidad genéricos y reducir el número de beans de entidad de la aplicación, y así reducir el número de beans deentidad de la aplicación.
  • Mapear directamente el modelo de objetos al modelo EJB trae beans de entidad específicos. Estos beans normalmente se mapean al esquema de la base de datos. Este mapeo de entidades-a-fila-de-base-de-datos causa problemas relacionados con el rendimiento, la manejabilidad, la seguridad y el manejo de transaciones. Las relaciones entre las tablas se implementan como relaciones entre beans de entidad, lo que significa que los beans de entidad contienen referencias a otros beans de entidad para implementar las relaciones específicas. Es muy costoso manejar las relaciones entre estos beans porque deben establecerse dinámicamente, utilizando los objetos home de los beans de entidad y las claves primarias de los beans enterprise.
  • Los clientes no necesitan conocer la implementación del esquema de la base de datos para utilizar y soportar los beans de entidad. Con beans de entidad específicos, el mapeo se hace normalmente para que cada ejemplar de bean de entidad corresponda con una sóla fila de la base de datos. Este mapeo específico crea una dependencia entre el cliente y el esquema de la base de datos subyacente, ya que el cliente trata con beans específicos y éstos esencialmente son una representación directa del esquema subyacente. Esto resulta en un fuerte acoplamineto entre el esquema de la base de datos y los beans de entidad. Un cambio en el esquema causa el cambio correspondiente en el bean de entidad, y además requiere el cambio correspondiente en los clientes.
  • Hay un incremento de la charlatanería de las aplicaciones debido a la intercomunicación entre los beans específicos. Esta excesiva comunicación entre los beans específicos frecuentemente provoca un cuello de botella en el rendimiento. Toda llamada a método del bean de entidad se hace mediante la capa de red, incluso si el llamador está en el mismo espacio de direcciones que el bean llamado (es decir, si tanto el cliente como el bean de entidad están en el mismo contenedor). Aunque algunos contenedores han optimizado este escenario, el desarrollador no puede esperar esta optimización en todos los contenedores.
  • Se puede observar una charlataneria adicional entre el cliente y los beans de entidad porque el cliente podría tener que comunicar con muchos beans de entidad específicos para cumplir los requerimientos. Es deseable reducir la comunicación entre los beans de entidad para reducir la charlataneria entre el cliente y la capa del bean de entidad.

. Solución

Utilizar Composite Entity para modelar, representar y manejar un conjunto de objetos persistentes relacionados en vez de representarlos como beans de entidad específicos individuales. Un bean Composite Entity representa un grupo de objetos.

Para entender esta solución, primero definamos que significan los objetos persistentes y discutiremos sus relaciones.

Un objeto persistente es un objeto que se almacena en algún tipo de almacenamiento. Normalmente múltiples clientes comparten los objetos persistentes. Los objetos persistentes se pueden clasificar en dos tipos: objetos genéricos y objetos dependientes.

Un objeto genérico es autosuficiente. Tiene su propio ciclo de vida y maneja sus relaciones con otros objetos. Todo objeto genérico podría referenciar o contener uno o más objetos. El objeto genérico normalmente maneja el ciclo devida de estos objetos. De ahí, que esos objetos sean conocidos como objetos dependientes. Un objeto dependiente puede ser un simple objeto auto-contenido o podría a su vez contener otros objetos dependientes.

El ciclo de vida de un objeto dependiente está fuertemente acoplado al ciclo de vida del objeto genérico. Un cliente sólo podría acceder al objeto dependiente através del objeto genérico. Es decir, los objetos dependientes no se exponen directamente a los clientes porque su objeto padre (genérico) los maneja. Los objetos dependientes no pueden existir por sí mismos. En su lugar, siempre necesitan tener su objeto genérido (o padre) para justificar su existencia.

Normalmente, podemos ver la relaciones entre un objeto genérico y sus objetos dependientes como un árbol. El objeto genérico es la raíz del árbol (el nodo raíz). Cada objeto dependiente puede ser un objeto dependientes solitario (un nodo hoja) que es hijo del objeto genérico. O, el objeto dependiente puede tener relaciones padre-hijo con otros objetos dependientes, en cuyo caso se considera un nodo rama.

Un bean Composite Entity puede representar un objeto genérico y todos sus objetos dependientes relacionados. Esta combinación de objetos persistentes relacionados dentro de un sólo bean de entidad reduce drásticamente el número de beans de entidad requeridos por la aplicación. Esto genera un bean de entidad altamente genérico que puede obtener mejor los beneficios de los beans de entidad que un bean de entidad específico.

Sin la aproximación Composite Entity, hay una tendencia a ver todos los objetos genéricos y dependientes como beans de entidad separados, suponiendo un gran número de beans de entidad.

. Estructura

Aunque hay muchas estrategias para implementar el patrón Composite Entity, la primera que vamos a explicar está representada en el siguiente diagrama de clases. Aquí el Composite Entity contiene el objeto genérico, y éste contiene objetos dependientes:

El diagrama de secuencia de la siguiente imagen muestra las interacciones para este patrón:

. Participantes y Responsabilidades

. CompositeEntity

CompositeEntity es un bean de entidad genérico. Podría ser un objeto genérico, o podría contener una referencia a un objeto genérico.

. Coarse-Grained Object

Un objeto genérico es un objeto que tiene su propio ciclo de vida y que maneja sus propias relaciones con otros objetos. Un objeto genérico puede ser un objeto Java contenido en el CompositeEntity. O, el propio Composite Entity puede ser un objeto genérico que contiene objetos dependientes.

. DependentObject1, DependentObject2, y DependentObject3

Un objeto dependiente es un objeto que depende de un objeto genérico el cual gobierna el ciclo de vida del objeto dependiente. Un objeto dependiente puede contener otros objetos dependientes; así podría existir un árbol de objetos dentro del Composite Entity.

. Estrategias

Esta sección explica las diferentes estrategias para implementar el patrón Composite Entity. Las estrategias consideran posibles alternativas y opciones para objetos persistentes (genéricos y dependientes), y la utilización deTransfer Objects.

. El Composite Entity Contiene Objetos Genéricos

En esta estrategia, el Composite Entity almacena o contiene el objeto genérico. Este objeto genérico continúa teniendo relaciones con sus objetos dependientes. La sección Estructuradescribe esta estrategia principal.

. El Composite Entity Implementa el Objeto Genérico

En esta estrategia, el propio Composite Entity es el objeto genérico y tiene atributos y métodos de objeto genérico. Los objetos dependientes son atributos del Composite Entity. Como el Composite Entity es el objeto genérico, el bean de entidad expresa y maneja todas las relaciones entre el objeto genérico y los objetos dependientes. La siguiente figura muestra el diagrama de clases de esta estrategia:

En esta otra figura podemos ver el diagrama de secuencia para esta estrategia:

. Lazy Loading

Un Composite Entity puede estar compuesto de muchos niveles de objetos dependientes en su árbol de objetos. Cargar todos los objetos dependientes cuando el contendor EJB llama al ejbLoad() del bean puede utilizar mucho tiempo y recursos. Una forma de optimizar esto es utilizar esta estrategia para cargar los objetos dependientes. Cuando se llama al método ejbLoad(), primero sólo carga aquellos objetos dependientes que son más cruciales para los clientes deComposite Entity. Luego, cuando el cliente accede a un objeto dependiente que no se haya cargado de la base de datos, el Composite Entity puede realizar una carga bajo demanda. Así, si no se utilizan algunos objetos dependientes, éstos no serán cargados en la inicialización. Sin embargo, cuando el cliente necesita dichos objetos dependientes, los carga en cualquier momento. Una vez que se han cargado los objetos dependientes, las siguientes llamadas al método ejbLoad() deben incluir esos objetos dependientes para recargar o sincronizar los cambios con el almacenamiento persistente.

. Store Optimization (Dirty Marker)

Un problema común con la persistencia controlada por el bean ocurre cuando almacenamos todo el conjunto de objetos durante una operación ejbStore(). Como el contendor EJB no tiene forma de conocer que datos han cambiado en el bean de entidad y en sus objetos dependientes, pone el balón en el lado del desarrollador para que determine qué y como almacenar. Algunos contenedores EJB proporcionan una característica para indentificar qué objetos del Composite Entity se necesita almacenar debido a una actualización anterior. Esto se podría hacer si los desarrolladores implementaran un método especial en los objetos dependientes, como isDirty(), al que llame el contenedor para comprobar si el objeto se ha actualizado desde la última operación ejbStore().

Una solución genérica podría ser utilizar un interface, DirtyMarker, como se ve en el diagrama de la siguiente figura. La idea es hacer que los objetos dependientes implementen el interface DirtyMarker para dejarle al llamador (normalmente el método ejbStore()) saber si el estado del objeto dependiente ha cambiado. De esta forma, el llamador puede elegir si obtener los datos para su almacenamiento posterior.

La siguiente figura contiene un diagrama de secuencia que muestra un ejemplo de interacción para esta estrategia.

El cliente realiza un actualización del Composite Entity, que resulta en un cambio en DependentObject3. Se accede a DependentObject3 mediante su padre DependentObject2. El Composite Entity es el padre de DependentObject2. Cuando se realiza la actualización, se llama al metodo setDirty() en DependentObject3. Posteriormente, cuando el contenedor llama al método ejbStore() sobre este ejemplar de Composite Entity, el método ejbStore() puede chequear qué objetos dependientes se han cambiado y grabar selectivamente en la base de datos aquellos que han cambiado. La marca dirty se resetea después de que la grabación haya tenido éxito.

El interface DirtyMarker también puede incluir métodos que reconozcan otros estados de persistencia del objeto dependiente. Por ejemplo, si se incluye un nuevo objeto dependiente dentro del Composite Entity, el método ejbStore() debería poder reconocer qué operación utilizar en este caso, el objeto dependiente no se ha modificado, sino que es nuevo. Extendiendo el interface DirtyMarker para incluir un método llamado isNewz(), el método ejbStore() puede invocar una operación de inserción en lugar de una operación de actualización. De forma similar, incluyendo un método llamado isDeleted(), el método ejbStore() puede invocar una operación de borrado cuando sea necesario.

En casos donde se llama a ejbStore() sin actualizaciones intermedias, del Composite Entity, ninguno de los objetos dependientes ha sido actualizado.

Esta estrategia evita la gran sobrecarga de tener que persistir todos los objetos dependientes en la base de datos siempre que el contenedor llame al método ejbStore().

Nota:
La especificación EJB 2.0 corrige las estrategias Lazy Loading y Store Optimization. Es posible utilizar estas estrategias en implementaciones anteriores a la EJB 2.0.
. Composite Transfer Object

Con un Composite Entity, un cliente puede obtener toda la información requerida con sólo una llamada a un método. Como el Composite Entity o implementa o contiene el objeto genérico y la herencia (o árbol) de objetos dependientes, puede crear el Transfer Object requerido y devolverlo al cliente aplicando el patrónTransfer Object. En la siguiente imagen podemos ver el diagrama de secuencia para esta estrategia:

El Transfer Object puede ser un objeto simple o compuesto que tenga subobjetos, dependiendo de los datos solicitados por el cliente. El Transfer Object es serializable y se pasa por valor al cliente. ElTransfer Object funciona sólo como un mero transmisor de objetos; no tiene responsabilidades con respecto a la seguridad, las transaciones, y la lógica de negocio. El Transfer Object empaqueta toda la información en un objeto, obteniendo la información con una llamada remota en vez de con múltiples llamadas. Una vez que el cliente recibe el Transfer Object, todas las llamadas posteriores del cliente al Transfer Object son llamadas locales.

La discusión apunta a cómo la entidad puede empaquetar todos sus datos en un Transfer Object compuesto y devolver este al cliente. Sin embargo,esta estrategia también permite al bean de entidad devolver sólo los datos que el cliente ha solicitado. Si el cliente sólo necesita los datos de un subconjunto de objetos dependientes, entonces el Transfer Object compuesto devuelto puede contener los datos derivados sólo de aquellas partes requeridas y no de todos los objetos dependientes. Esto podría ser una aplicación de la estrategia Multiple Transfer Objects del patrón Transfer Object.

. Consecuencias

  • Elimina las Relaciones Inter-Entidades
    Utilizando el patrón Composite Entity, los objetos dependientes se unen en un sólo bean de entidad, eliminando todas las relaciones entre beans de entidad. Este patrón proporciona un lugar central para manejar las relaciones y la herencia de los objetos.
  • Mejora la Manejabilidad Reduciendo los Beans de Entidad
    Como se ha explicado, la implementación de objetos persistentes como beans de entidad específicos resulta en un mayor número de clases que necesitamos desarrollar y mantener. Utilizar Composite Entity reduce el número de clases EJB y el código, y hace más fácil el mantenimiento. Mejora la manejabilidad de la aplicación teniendo un menor número de componentes genéricos en lugar de tener muchos más componentes específicos.
  • Mejora el Rendimiento de Red
    La unión de objetos dependientes mejora el rendimiento general. Esta unión elima toda la comunicación específica entre objetos dependientes a través de la red. Si todos los objetos dependientes se diseñaran como beans de entidad específicos, provocaría una sobrecarga en la red debido a la comunicación entre los beans de entidad.
  • Reduce la Dependencia del Esquema de la Base de Datos
    Cuando se utiliza el patrón Composite Entity, resulta en implementaciones de beans de entidad genéricos. El esquema de la base de datos está oculto para los clientes, ya que el mapeo entre el bean de entidad y el esquema es interno del bean de entidad. Los cambios en el esquema de la base de daos podrían requerir cambios en los beans de CompositeEntity. Sin embargo, los clientes no se ven afectados porque los beans Composite Entity no exponen el esquema al mundo exterior.
  • Incrementa la Generalidad del Objeto
    Con un Composite Entity, el cliente normalmente busca un sólo bean de entidad en lugar de una gran cantidad de beans de entidad específicos. El cliente le pide los datos al Composite Entity. Éste puede crear un Transfer Object compuesto que contenga todos los datos del bean de entidad y devolverlo al cliente en una única llamada a método.
  • Facilita la Creación de Transfer Object Compuestos
    Utilizando esta estrategia, se reduce la comunicación entre el cliente y el bean de entidad, ya que el bean Composite Entity puede devolver un Transfer Object compuesto proporcionando un mecanismo para enviar Transfer Objects serializados. Aunque un Transfer Object devuelva todos los datos en una llamada remota, la cantidad de datos devueltos con esta única llamada es mucho mayor que la cantidad de datos devuelta por llamadas remotas independientes para obtener propiedades de beans de entidad individuales. Este inconveniente se puede sobrellevar cuando el objetivo es evitar la repetición de llamadas remotas y búsquedas múltiples.
  • Sobrecarga de Grupos de Objetos Dependientes Multi-Nivel
    Si el conjunto de objetos dependientes manejado por el Composite Entity tiene muchos niveles, entonces se incrementa la carga y almacenamiento de objetos dependientes. Esto se puede reducir utilizando estrategias de optimización para la carga y almacenamiento, peo entonces podría aparecer otra sobrecarga asociada con el chequeo de objetos obsoletos para su almacenamiento y la carga de los objetos requeridos.

. Codigo de Ejemplo

Consideremos una aplicación de Servicio Profesional de automatización (PSA) donde se implementa un objeto de negocio: Resource utilizando el patrón Composite Entity. Resource representa el recurso "empleado" que está asignado a los proyectos. Cada objeto Resource puede tener diferentes objetos dependientes, de esta forma:

  • BlockOutTime - Este objeto dependiente representa el periodo de tiempo que el recurso no está disponible por razones como formación, vacaciones, días libres, etc. Como cada recurso puede tener varios periodos de absentismo, la relación Resource-a-BlockOutTime es de uno-a-muchos.
  • SkillSet - Este objeto dependiente representa las "Habilidades" (Skills) que posee un recurso. Como cada recurso puede tener mútiples habilidades, la relación de Resource-a-SkillSet es de uno a muchos.

. Implementación del patrón Composite Entity

El patrón para el objeto de negocio Resource se implementa como un Composite Entity (ResourceEntity), como se ve en el siguiente código. La relación uno-a-muchos con sus objetos dependientes (objetos BlockOutTime y SkillSet) se implementa utilizando collections.

package corepatterns.apps.psa.ejb;

import corepatterns.apps.psa.core.*;
import corepatterns.apps.psa.dao.*;
import java.sql.*;
import javax.sql.*;
import java.util.*;
import javax.ejb.*;
import javax.naming.*;

public class ResourceEntity implements EntityBean {
  public String employeeId;
  public String lastName;
  public String firstName;
  public String departmentId;
  public String practiceGroup;
  public String title;
  public String grade;
  public String email;
  public String phone;
  public String cell;
  public String pager;
  public String managerId;
  
  // Collection of BlockOutTime Dependent objects
  public Collection blockoutTimes;

  // Collection of SkillSet Dependent objects
  public Collection skillSets;

  ...  

  private EntityContext context;
// Entity Bean methods implementation
public String ejbCreate(ResourceTO resource) throws 
  CreateException {
    try {
      this.employeeId = resource.employeeId;
      setResourceData(resource);
      getResourceDAO().create(resource);
    } catch(Exception ex) {
      throw new EJBException("Reason:" + ...);
    }
    return this.employeeId;
}
  
public String ejbFindByPrimaryKey(String primaryKey) 
  throws FinderException {
    boolean result;
    try {
      ResourceDAO resourceDAO = getResourceDAO();
      result = 
        resourceDAO.selectByPrimaryKey(primaryKey);
    } catch(Exception ex) {
      throw new EJBException("Reason:" + ...);
    }
    if(result) {
      return primaryKey;
    }
    else {
      throw new ObjectNotFoundException(...);
    }
  }
  
  public void ejbRemove() {
    try {
      // Remove dependent objects
      if(this.skillSets != null) {

        SkillSetDAO skillSetDAO = getSkillSetDAO();
        skillSetDAO.setResourceID(employeeId);
        skillSetDAO.deleteAll();
        skillSets = null;
      }
      if(this.blockoutTime != null) {
        BlockOutTimeDAO blockouttimeDAO = 
            getBlockOutTimeDAO();
        blockouttimeDAO.setResourceID(employeeId);
        blockouttimeDAO.deleteAll();
        blockOutTimes = null;
      }

      // Remove the resource from the persistent store
      ResourceDAO resourceDAO = new 
        ResourceDAO(employeeId);
      resourceDAO.delete();
    } catch(ResourceException ex) {
      throw new EJBException("Reason:"+...);
    } catch(BlockOutTimeException ex) {
      throw new EJBException("Reason:"+...);
    } catch(Exception exception) {
      ...
    }
  }
  public void setEntityContext(EntityContext context) 
  {
    this.context = context;
  }
  
  public void unsetEntityContext() {
    context = null;
  }
  
  public void ejbActivate() {
    employeeId = (String)context.getPrimaryKey();
  }
  
  public void ejbPassivate() {
    employeeId = null;
  }
  
  public void ejbLoad() {
    try {
      // load the resource info from
      ResourceDAO resourceDAO = getResourceDAO();
      setResourceData((ResourceTO) 
        resourceDAO.load(employeeId));
      
      // Load other dependent objects, if necessary
      ...
    } catch(Exception ex) {
      throw new EJBException("Reason:" + ...);
    }
  }
  
  public void ejbStore() {
    try {
      // Store resource information
      getResourceDAO().update(getResourceData());

      // Store dependent objects as needed
      ...
    } catch(SkillSetException ex) {
      throw new EJBException("Reason:" + ...);
    } catch(BlockOutTimeException ex) {
      throw new EJBException("Reason:" + ...);
    }
    ...
  }
  public void ejbPostCreate(ResourceTO resource) {
  }

  // Method to Get Resource Transfer Object
  public ResourceTO getResourceTO() {
    // create a new Resource Transfer Object
    ResourceTO resourceTO = new 
        ResourceTO(employeeId);

    // copy all values 
    resourceTO.lastName = lastName;
    resourceTO.firstName = firstName;
    resourceTO.departmentId = departmentId;
    ...
    return resourceTO;
  }

  public void setResourceData(ResourceTO resourceTO) {
    // copy values from Transfer Object into entity bean
    employeeId = resourceTO.employeeId;
    lastName = resourceTO.lastName;
    ...
  }

  // Method to get dependent Transfer Objects
  public Collection getSkillSetsData() {
    // If skillSets is not loaded, load it first.
    // See Lazy Load strategy implementation.

    return skillSets;  
  }
  ...

  // other get and set methods as needed
  ...

  // Entity bean business methods
  public void addBlockOutTimes(Collection moreBOTs) 
  throws BlockOutTimeException {
    // Note: moreBOTs is a collection of 
    // BlockOutTimeTO objects
    try {
      Iterator moreIter = moreBOTs.iterator();
      while(moreIter.hasNext()) {
        BlockOutTimeTO botTO = (BlockOutTimeTO)moreIter.next();
        if (! (blockOutTimeExists(botTO))) {
          // add BlockOutTimeTO to collection
          botTO.setNew();
          blockOutTime.add(botTO);
        } else {
          // BlockOutTimeTO already exists, cannot add
          throw new BlockOutTimeException(...);
        }
      }
    } catch(Exception exception) {
      throw new EJBException(...);
    }
  }

  public void addSkillSet(Collection moreSkills) 
  throws SkillSetException {
    // similar to addBlockOutTime() implementation
    ...
  }

  ...

  public void updateBlockOutTime(Collection updBOTs) 
  throws BlockOutTimeException {
    try {
      Iterator botIter = blockOutTimes.iterator();
      Iterator updIter = updBOTs.iterator();
      while (updIter.hasNext()) {
        BlockOutTimeTO botTO = (BlockOutTimeTO)
          updIter.next();
        while (botIter.hasNext()) {
          BlockOutTimeTO existingBOT = 
            (BlockOutTimeTO) botIter.next();
          // compare key values to locate BlockOutTime
          if (existingBOT.equals(botTO)) {
            // Found BlockOutTime in collection
            // replace old BlockOutTimeTO with new one
            botTO.setDirty(); //modified old dependent
            botTO.resetNew(); //not a new dependent
            existingBOT = botTO;
          }
        }
      }
    } catch (Exception exc) {
      throw new EJBException(...);
    }
  }

  public void updateSkillSet(Collection updSkills) 
  throws CommitmentException {
    // similar to updateBlockOutTime...
    ...
  }

  ...

}

. Implementar la Estrategia Lazy Loading

Cuando el contenedor carga por primera vez el Composite Entity utilizando el método ejbLoad(), asumimos que sólo se han cargado los datos del recurso. Esto incluye los atributos listados en el bean ResourceEntity, excluyendo las colecciones de objetos dependientes. Los objetos dependientes entonces sólo se pueden cargar si el cliente invoca un método de negocio que necesita que se carguen esos objetos dependientes. Consiguientemente, el método ejbLoad() necesita seguir la pista de los objetos dependientes cargados de esta manera e incluirlos para su recarga.

Abajo podemos ver los métodos más importantes de la clase ResourceEntity:


...
public Collection getSkillSetsData() {
throws SkillSetException {
  checkSkillSetLoad();
  return skillSets;
}

private void checkSkillSetLoad() 
throws SkillSetException {
  try {
    // Lazy Load strategy...Load on demand
    if (skillSets == null)
      skillSets = 
        getSkillSetDAO(resourceId).loadAll();
  } catch(Exception exception) {
    // No skills, throw an exception 
    throw new SkillSetException(...);
  }
}

...

public void ejbLoad() {
  try {
    // load the resource info from
    ResourceDAO resourceDAO = new 
      ResourceDAO(employeeId);
    setResourceData((ResourceTO)resourceDAO.load());
      
    // If the lazy loaded objects are already
    // loaded, they need to be reloaded.
    // If there are not loaded, do not load them
    // here...lazy load will load them later.
    if (skillSets != null) {
      reloadSkillSets();
    }
    if (blockOutTimes != null) {
      reloadBlockOutTimes();
    }
    ...
    throw new EJBException("Reason:"+...);
  }
}
...

. Implementar la Estrategia Store Optimization (Dirty Marker)

Para utilizar la estrategia Store Optimization, necesitamos que los objetos dependientes implementen el interface DirtyMarker, como se ve en el siguiente ejemplo:

public class SkillSetTO implements DirtyMarker, 
  java.io.Serializable {
  private String skillName;
  private String expertiseLevel;
  private String info;
  ...

  // dirty flag
  private boolean dirty = false;

  // new flag
  private boolean isnew = true;

  // deleted flag
  private boolean deleted = false;

  public SkillSetTO(...) {
    // initialization
    ...
    // is new TO
    setNew();
  }

  // get, set and other methods for SkillSet
  // all set methods and modifier methods
  // must call setDirty()
  public setSkillName(String newSkillName) {
    skillName = newSkillName;
    setDirty();
  }
  ...

  // DirtyMarker methods
  // used for modified Transfer Objects only
  public void setDirty() {
    dirty = true;
  }
  public void resetDirty() {
    dirty = false;
  }
  public boolean isDirty() {
    return dirty;
  }

  // used for new Transfer Objects only
  public void setNew() {
    isnew = true;
  }
  public void resetNew() {
    isnew = false;
  }
  public boolean isNew() {
    return isnew;
  }

  // used for deleted objects only
  public void setDeleted() {
    deleted = true;
  }
  public boolean isDeleted() {
    return deleted;
  }
  public void resetDeleted() {
    deleted = false;
  }

}

En el siguiente ejemplo podemos ver el método ejbStore() optimizado para utilizarlo con esta estrategia:

...

  public void ejbStore() {
    try {
      // Load the mandatory data
      getResourceDAO().update(getResourceData());

      // Store optimization for dependent objects
      // check dirty and store
      // Check and store commitments
      if (skillSets != null) {
        // Get the DAO to use to store
        SkillSetDAO skillSetDAO = getSkillSetDAO();
        Iterator skillIter = skillSet.iterator();
        while(skillIter.hasNext()) {
          SkillSetTO skill = 
            (SkillSetTO) skillIter.next();
          if (skill.isNew()) {
            // This is a new dependent, insert it
            skillSetDAO.insert(skill);
            skill.resetNew();
            skill.resetDirty();
          }
          else if (skill.isDeleted()) {
            // delete Skill
            skillSetDAO.delete(skill);
            // Remove from dependents list
            skillSets.  remove(skill);
          } 
          else if (skill.isDirty()) {
            // Store Skill, it has been modified
            skillSetDAO.update(skill);
            // Saved, reset dirty.
            skill.resetDirty();
            skill.resetNew();
          }
        }
      }

      // Similarly, implement store optimization 
      // for other dependent objects such as 

      // BlockOutTime, ...
      ...
    } catch(SkillSetException ex) {
      throw new EJBException("Reason:"+...);
    } catch(BlockOutTimeException ex) {
      throw new EJBException("Reason:"+...);
    } catch(CommitmentException ex) {
      throw new EJBException("Reason:"+...);
    }
  }

  ...

. Implementar la Estrategia Composite Transfer Object

Ahora consideremos el requerimiento donde el cliente necesita obtener todos los datos del ResourceEntity, y no sólo una parte. Esto se puede hacer utilizando la estrategia Composite Transfer Object como se muestra en el siguiente ejemplo:

public class ResourceCompositeTO {
  private ResourceTO resourceData;
  private Collection skillSets;
  private Collection blockOutTimes;

  // Transfer Object constructors
  ...

  // get and set methods
  ...
}

El ResourceEntity proporciona un método getResourceDetailsData() para devolver el objeto Transfer Object compuesto ResourceCompositeTO:

...
public ResourceCompositeTO getResourceDetailsData() {
  ResourceCompositeTO compositeTO =
    new ResourceCompositeTO (getResourceData(),
        getSkillsData(), getBlockOutTimesData());
  return compositeTO;
}
...

. Patrones Relacionados

  • Transfer Object
    El patrón Composite Entity usa el patrón Transfer Object para crear el objeto que devuelve al cliente. El patrón Transfer Object se utiliza para serializar el árbol de objetos genéricos y de objetos dependientes, o parte de ese árbol, según se requiera.
  • Session Facade
    Si los objetos dependientes tienden a ser beans de entidad en vez de objetos Java normales, debemos intentar de utilizar el patrón Session Facade para manejar las relaciones entre beans de entidad.
  • Transfer Object Assembler
    Este patrón es similar al patrón Transfer Object. Sin embargo, en este caso, las fuentes de datos para todos los Transfer Objects del compuesto son parte del propio Composite Entity, mientras que para el Transfer Object Assembler, los datos puede ser diferentes beans de entidad, beans de sesión, DAOs, objetos Java, etc.

. Bean de Entidad como un Objeto Dependiende: Problemas y Recomendaciones

Típicamente, diseñamos los objetos dependientes como objetos Java que tienen una relación directa con el objeto genérico padre. Sin embargo, podría hacer situaciones en las que un objeto dependiente podría aparecer como un propio bean de entidad:

  1. Si el objeto dependiente es dependiente de dos objetos padre diferentes (como es el caso de la asociación de clases.
  2. Si el objeto dependiente ya existe como un bean de entidad en la misma aplicación o se importa desde una aplicación diferente.

En estos casos, el ciclo de vida del objeto dependiente podría parecer que no está directamente relacionado y manejado por un sólo objeto genérico padre. Entonces, ¿qué podemos hacer cuando un objeto dependiente es un bean de entidad? ¿y, cuándo veamos un objeto dependiente que no es totalmente dependiente de su objeto padre? ¿o, cuándo no podamos identificar su único objeto padre?

Consideremos cada caso con un poco más de detalle:

  • Caso 1: El Objeto Dependiente depende de Dos Objetos Padre.

    Exploremos esto con el siguiente ejemplo. Un Commitment representa una asociación entre un Resource y un Project. La siguiente figura muestra un ejemplo de diagrama de clases con las relaciones entre Project, Resource y Commitment.

    Commitment es un objeto dependiente. Projects y Resources son objetos genéricos. Cada Project tiene una relación uno-a-muchos con objetos Commitment. Entonces. ¿Commitment es un objeto dependiente de Project o de Resource? La respuesta trata de analizar las interacciones para los casos de utilización que implican estos tres componentes. Si hacemos que el objeto Commitment sea dependiente del objeto Project, entonces cuando un Resource acceda a su lista de objetos Commitment, tiene que hacerlo a través del objeto Project. Por otro lado, si Commitment es un objeto dependiente de un Resource, cuando Project acceda a su lista de objeto Commitment, tiene que hacerlo mediante el Resource. Estas dos elecciones introducen en el diseño las relaciones bean-de-entidad-a-bean-de-entidad.

    Pero, ¿qué pasa si Commitment es un bean de entidad en vez de un objeto dependiente? Entonces la relación entre Project y su lista de objetos Commitment, y entre un Resource y su lista de objetos Commitment, será una relación entidad-a-bean-de-entidad. Esto empeora el problema en que ahora tenemos dos relaciones bean-de-entidad-a-bean-de-entidad.

    Este tipo de relaciones no es recomendable debido a la sobrecarga asociada con su manejo y sustentación.

  • Caso 2: El Objeto Dependiente ya existe como un Bean de Entidad

    En este caso, podría parecer que una forma de modelar esta relación es almacenar la clave primaria del objeto dependitne en el objeto genérico. Cuando el objeto genérico necesite acceder al objeto dependiente, el resultado será una llamada bean-de-entidad-a-bean-de-entidad. Abajo podemos ver el diagrama de clases para este ejemplo:

    En la siguiente figura podemos ver el diagrama de secuencia para este escenario. El Composite Entity utiliza las referencias al objeto dependiente para buscar el bean de entidad requerido. En este caso el objeto dependiente es un proxy al bean de entidad dependiente:

    Aunque esto podría corrige el requerimiento de utilizar un bean de entidad dependiente partiendo de un bean de entidad padre, no es una solución elegante. En su lugar, para evitar la complejidad del diseño y manejo de las relaciones entre-entidades, consideremos la utilización de un bean de sesión para manejar las relaciones entre los beans de entidad. Según nuestra experiencia, hemos encontrado que el patrón Session Facade nos ayudó a solucionar este problema y nos proporcionó una mejor forma de manejar las relaciones bean-de-entidad-a-bean-de-entidad.

    Por eso, recomendamos evitar las relaciones bean-de-entidad-a-bean-de-entidad como la mejor práctica, y para optimizar las relaciones dentro de un bean de sesión recomendamos la utilización del patrón Session Facade.

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.