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

El acceso a los datos varía dependiendo de la fuente de los datos. El acceso al almacenamiento persistente, como una base de datos, varía en gran medida dependiendo del tipo de almacenamiento (bases de datos relacionales, bases de datos orientadas a objetos, ficheros planos, etc.) y de la implementación del vendedor.

. Problema

Muchas aplicaciones de la plataforma J2EE en el mundo real necesitan utilizar datos persistentes en algún momento. Para muchas de ellas, este almacenamiento persistente se implementa utilizando diferentes mecanismos, y hay marcadas diferencias en los APIS utilizados para acceder a esos mecanismos de almacenamiento diferentes. Otras aplicaciones podrían necesitar acceder a datos que residen en sistemas diferentes. Por ejemplo, los datos podrían residir en sitemas mainframe, repositorios LDAP, etc. Otro ejemplo es donde los datos los proporcionan servicios a través de sistemas externos como los sistemas de integración negocio-a-negocio (B2B), servicios de tarjetas de crédito, etc.

Normalmente, las aplicaciones utilizan componentes distribuidos y compartidos como los beans de entidad para representar los datos persistentes. Se considera que una aplicación emplea consistencia manejada por el bean (BMP) cuando sus beans de entidad acceden explícitamente al almacenamiento persistente -- el bean de entidad incluye código para hacer esto. Una aplicación con requerimientos sencillos podría por lo tanto utilizar beans de entidad en lugar de beans de sesión o servlets para acceder al almacenamiento persistente y recuperar o modificar los datos. O, la aplicación podría usar beans de entidad con persistencia manejada por el contenedor, y así dejar que el contenedor maneje los detalles de las transaciones y de la persistencia.

Las aplicaciones pueden utilizar el API JDBC para acceder a los datos en un sistema de control de bases de datos relacionales (RDBMS). Este API permite una forma estándar de acceder y manipular datos en un almacenamineto persistente, como una base de datos ralacional. El API JDBC permite a las aplicaciones J2EE utilizar sentencias SQL, que son el método estándar para acceder a tablas RDBMS. Sin embargo, incluso dentro de un entorno RDBMS, la síntaxis y formatos actuales de las sentencias SQL podrían variar dependiendo de la propia base de datos en particular.

Incluso hay una mayor variación con diferentes tipos de almacenamientos persistentes. Los mecanimos de acceso, los APIs soportados, y sus características varian entre los diferentes tipos de almacenamientos persistentes, como bases de datos relacionales, bases de datos orientadas a objetos, ficheros planos, etc. Las aplicaciones que necesitan acceder a datos de un sistema legal o un sistema dispar (como un mainframe o un servicio B2B) se ven obligados a utilizar APIs que podrían ser propietarios. Dichas fuentes de datos dispares ofrecen retos a la aplicación y potencialmente pueden crear una dependencia directa entre el código de la aplicación y el código de acceso a los datos. Cuando los componentes de negocio -- beans de entidad, beans de sesión e incluso componentes de presentación como servlets y beans de apoyo para páginas JSP -- necesitan acceder a una fuente de datos, pueden utilizar el API apropiado para conseguir la conectividad y manipular la fuente de datos. Pero introducir el código de conectividad y de acceso a datos dentro de estos componentes genera un fuerte acoplamiento entre los componentes y la implementación de la fuente de datos. Dichas dependencias de código en los componentes hace difícil y tedioso migrar la aplicación de un tipo de fuente de datos a otro. Cuando cambia la fuente de datos, también deben cambiar los componentes para manejar el nuevo tipo de fuente de datos.

. Causas

  • Los componentes como los beans de entidad controlados por el bean, los beans de sesión, los servlets, y otros objetos como beans de apoyo para páginas JSP necesitan recuperar y almacenar información desde almacenamientos persistentes y otras fuentes de datos como sistemas legales, B2B, LDAP, etc.
  • Los APIs para almacenamiento persistente varían dependiendo del vendedor del producto. Otras fuentes de datos podrían tener APIS que no son estándar y/o propietarios. Estos APIs y sus capacidades también varían dependiendo del tipo de almacenamiento -- bases de datos relacionales, bases de datos orientadas a objetos, documentos XML, ficheros planos, etc. Hay una falta de APIs uniformes para corregir los requrimientos de acceso a sistemas tan dispares.
  • Los componentes normalmente utilizan APIs propietarios para acceder a sistemas externos y/o legales para recuperar y almacenar datos.
  • La portabilidad de los componentes se ve afectada directamente cuando se incluyen APIs y mecanismos de acceso específicos.
  • Los componentes necesitan ser transparentes al almacenamiento persistente real o la implementación de la fuente de datos para proporcionar una migración sencilla a diferentes productos, diferentes tipos de almacenamiento y diferentes tipos de fuentes de datos.

. Solución

Utilizar un Data Access Object (DAO) para abstraer y encapsular todos los accesos a la fuente de datos. El DAO maneja la conexión con la fuente de datos para obtener y almacenar datos.

El DAO implementa el mecanismo de acceso requerido para trabajar con la fuente de datos. Esta fuente de datos puede ser un almacenamiento persistente como una RDMBS, un servicio externo como un intercambio B2B, un repositorio LDAP, o un servicio de negocios al que se accede mediante CORBA Internet Inter-ORB Protocol (IIOP) o sockets de bajo nivel. Los componentes de negocio que tratan con el DAO utilizan un interface simple expuesto por el DAO para sus clientes. El DAO oculta completamente los detalles de implementación de la fuente de datos a sus clientes. Como el interface expuesto por el DAO no cambia cuando cambia la implementación de la fuente de datos subyacente, este patrón permite al DAO adaptarse a diferentes esquemas de almacenamiento sin que esto afecte a sus clientes o componentes de engocio. Esencialmente, el DAO actúa como un adaptador entre el componente y la fuente de datos.

. Estructura

La siguiente figura muestra el diagrama de clases que representa las relaciones para el patrón DAO:

. Participantes y Responsabilidades

La siguiente figura muestra el diagrama de secuecnia de la interacción entre los distintos participantes en este patrón:

. BusinessObject

BusinessObject representa los datos del cliente. Es el objeto que requiere el acceso a la fuente de datos para obtener y almacenar datos. Podríamos implementar un BusinessObject como un bean de sesión, un bean de entidad o cualquier otro objeto Java, además de como un Servlet o como un bean de apoyo.

. DataAccessObject

DataAccessObject es el objeto principal de este patrón. DataAccessObject abstrae la implementación del acceso a datos subyacente al BusinessObject para permitirle un acceso transparente a la fuente de datos. El BusinessObject también delega las operaciones de carga y almacenamiento en el DataAccessObject.

. DataSource

Representa la implementación de la fuente de datos. Una fuente de datos podría ser una base de datos como un RDBMS, un OODBMS, un repositorio XML, un fichero plano, etc. También lo pueden ser otros sitemas (mainframes/legales), servicios (servicio B2B u oficina de tarjetas de crédito), o algún tipo de repositorio (LDAP).

. TransferObject

Representa un Transfer Object utilizado para el transporte de datos. DataAccessObject podría utilizar un Transfer Object para devolver los datos al cliente. El DataAccessObject también podría recibir datos desde el cliente en un Transfer Object para actualizar los datos en la fuente de datos.

. Estrategias

. Automatic DAO Code Generation

Como cada BusinessObject corresponde a un DAO específico, es posible establecer relaciones entre el BusinessObject, el DAO, y las implementaciones subyacentes (como en las tablas de una RDBMS). Una vez que se han establecido las relaciones, es posible escribir una utilidad de generación-de-código-específica-de-aplicación que genere el código para todos los DAOs que necesita la aplicación. Los metadatos para generar el DAO pueden venir de un fichero descriptor definido por el desarrollador. Como alternativa, el generador de código puede inspeccionar la base de datos y proporcionar los DAOs necesarios para acceder a ella.

Si los requerimientos de los DAOs son lo suficientemente complejos, debemos considerar la utilización de herramientas de terceras partes que proporcionan un mapeo de objeto-a-relacional para bases de datos RDBMS. Estas herramientas normalmente incluyen utilidades GUI para mapear los objetos de negocio a objetos de almacenamiento persistente y además definir los DAOs intermedios. Estas herramientas generan el código automáticamente una vez que se termina el mapeo, y podrían proporcionar otras características valiosas como el caché de resultados y de consultas, integración con servidores de aplicaciones, integración con otros productos de terceras partes, etc.

. Factory for Data Access Objects

El patrón DO se puede flexibilizar adoptando los patrones Abstract Factory [GoF] y Factory Method [GoF].

Cuando el almacenamiento subyacente no está sujeto a cambios de una implemantación a otra, esta estrategia se puede implementar utilizando el patrón Factory Method para producir el número de DAOs que necesita la aplicación. En la siguiente figura podemos ver el diagrama de clases para este caso:

Cuando el almacenamiento subyacente si está sujeto a cambios de una implementación a otra, esta estrategia se podría implementar usando el patrón Abstract Factory. Este patrón a su vez puede construir y utilizar la implementación Factory Method. En este caso, esta estrategia proporciona un objeto factoría abstracta de DAOs (Abstract Factory) que puede construir varios tipos de factorías concretas de DAOs, cada factoría soporta un tipo diferente de implementación del almacenamiento persistente. Una vez que obtenemos la factoría concreta de DAOs para una implementación específica, la utilizamos para producir los DAOs soportados e implementados en esa implementación.

En la siguiente figura podemos ver el diagrama de clases para esta estrategia. En él vemos una factoría base de DAOs, que es una clase abstracta que extienden e implementan las diferentes factorías concretas de DAOs para soportar el acceso específico a la implementación del almacenamiento. El cliente puede obtener una implementación de la factoría concreta del DAO como una RdbDAOFactory y utilizarla para obtener los DAOs concretos que funcionan en la implementación del almacenamiento. Por ejemplo, el cliente puede obtener una RdbDAOFactory y utilizarlas para obtener DAOs específcios como RdbCustomerDAO, RdbAccountDAO, etc. Los DAOs pueden extender e implementar una clase base genérica (mostradas como DAO1 y DAO2) que describa específicamente los requerimientos del DAO para el objeto de negocio que soporta. Cada DAO concreto es responsable de conectar con la fuente de datos y de obtener y manipular los datos para el objeto de negocio que soporta.

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

. Consecuencias

  • Permite la Transpariencia
    Los objetos de negocio puede utilizar la fuente de datos sin conocer los detalles específicos de su implementación. El acceso es transparente porque los detalles de la implementación se ocultan dentro del DAO.
  • Permite una Migración más Fácil
    Una capa de DAOs hace más fácil que una aplicación pueda migrar a una implementación de base de datos diferente. Los objetos de negocio no conocen la implementación de datos subyacente, la migración implica cambios sólo en la capa DAO. Admás, si se emplea la estrategia de factorías, es posible proporcionar una implementación de factorías concretas por cada implementación del almacenamiento subyacente. En este caso, la migración a un almacenamiento diferente significa proporcionar a la aplicación una nueva implementación de la factoría.
  • Reduce la Complejidad del Código de los Objetos de Negocio
    Como los DAOs manejan todas las complejidades del acceso a los datos, se simplifica el código de los objetos de negocio y de otros clientes que utilizan los DAOs. Todo el código relacionado con la implementación (como las sentencias SQL) están dentro del DAO y no en el objeto de negocio. Esto mejora la lectura del código y la productividad del desarrollo.
  • Centraliza Todos los Accesos a Datos en un Capa Indenpendinte
    Como todas las operaciones de acceso a los datos se ha delegado en los DAOs, esto se puede ver como una capa que aisla el resto de la aplicación de la implementación de acceso a los datos. Esta centralización hace que la aplicación sea más sencilla de mantener y de manejar.
  • No es útil para Persistencia Manejada por el Contenedor
    Como el contenedor EJB maneja los beans de entidad con persistencia manejada por el contenedor (CMP), sirve automáticamente todo el acceso al almacenamiento persistente. Las aplicación que utilizan este tipo de beans no necesitan la capa DAO, ya que el servidor de aplicaciones proporciona de forma transparente esta funcionalidad. Sin embargo, los DAOS aún son útiles cuando se necesita una combinación de CMP (para beans de entidad) y BMP (para beans de seión, servlets).
  • Añade una Capa Extra
    Los DAOs crean un capa de objetos adicional entre el cliente y la fuente de datos que necesitamos diseñar e implementar para obtener los beneficios de este patrón. Pero para obtener estos beneficios debemos pagarlos con un esfuerzo adicional.
  • Necesita Diseñar un Árbol de Clases
    Cuando se utiliza una estrategia de factorías, necesitamos diseñar e implementar el árbol de factorías concretas y el árbol de productos concretos producidos por las factorías. Necesitamos justificar este esfuerzo adicional para ver si merece la pena dicha flexibilidad. Esto incrementa la complejidad del diseño. Sin embargo, podemos elegir la implementación de la estrategia de factorías empezando primero con el patrón Factory Method, y luego avanzar hasta el patrón Abstract Factory si es necesario.

. Código de Ejemplo

. Implementar el Patrón Data Access Object

Abajo podemos ver el código de un DAO de ejemplo para un objeto persistente que representa información de un cliente. CloudscapeCustomerDAO crea un Transfer Object Customer cuando se llama al método findCustomer():

// CloudscapeCustomerDAO implementation of the 
// CustomerDAO interface. This class can contain all
// Cloudscape specific code and SQL statements. 
// The client is thus shielded from knowing 
// these implementation details.

import java.sql.*;

public class CloudscapeCustomerDAO implements 
    CustomerDAO {
  
  public CloudscapeCustomerDAO() {
    // initialization 
  }

  // The following methods can use
  // CloudscapeDAOFactory.createConnection() 
  // to get a connection as required

  public int insertCustomer(...) {
    // Implement insert customer here.
    // Return newly created customer number
    // or a -1 on error
  }
  
  public boolean deleteCustomer(...) {
    // Implement delete customer here
    // Return true on success, false on failure
  }

  public Customer findCustomer(...) {
    // Implement find a customer here using supplied
    // argument values as search criteria
    // Return a Transfer Object if found,
    // return null on error or if not found
  }

  public boolean updateCustomer(...) {
    // implement update record here using data
    // from the customerData Transfer Object
    // Return true on success, false on failure or
    // error
  }

  public RowSet selectCustomersRS(...) {
    // implement search customers here using the
    // supplied criteria.
    // Return a RowSet. 
  }

  public Collection selectCustomersTO(...) {
    // implement search customers here using the
    // supplied criteria.
    // Alternatively, implement to return a Collection 
    // of Transfer Objects.
  }
  ...
}

Abajo podemos ver el código para utilizar este DAO:

...
// create the required DAO Factory
DAOFactory cloudscapeFactory =   
  DAOFactory.getDAOFactory(DAOFactory.DAOCLOUDSCAPE);

// Create a DAO
CustomerDAO custDAO = 
  cloudscapeFactory.getCustomerDAO();

// create a new customer
int newCustNo = custDAO.insertCustomer(...);

// Find a customer object. Get the Transfer Object.
Customer cust = custDAO.findCustomer(...);

// modify the values in the Transfer Object.
cust.setAddress(...);
cust.setEmail(...);
// update the customer object using the DAO
custDAO.updateCustomer(cust);

// delete a customer object
custDAO.deleteCustomer(...);
// select all customers in the same city 
Customer criteria=new Customer();
criteria.setCity("New York");
Collection customersList = 
  custDAO.selectCustomersTO(criteria);
// returns customersList - collection of Customer
// Transfer Objects. iterate through this collection to
// get values.

...

La siguiente figura representa el diagrama de clases para este ejemplo:

. Implementar la Estrategia Factory for Data Access Objects

. Utilizar el Patrón Factory Method

Consideremos un ejemplo donde hemos implementado esta estrategia en la que una factoría de DAOs produce muchos DAOs para una única implementación de una base de datos (por ejemplo, Oracle). La factoría produce DAOs como CustomerDAO, AccountDAO, OrderDAO, etc. Abajo podemos ver el diagrama de clases para este ejemplo:

Y aquí tenemos el código de ejemplo para la factoría de DAOs (CloudscapeDAOFactory):

// Cloudscape concrete DAO Factory implementation
import java.sql.*;

public class CloudscapeDAOFactory extends DAOFactory {
  public static final String DRIVER=
    "COM.cloudscape.core.RmiJdbcDriver";
  public static final String DBURL=
    "jdbc:cloudscape:rmi://localhost:1099/CoreJ2EEDB";

  // method to create Cloudscape connections
  public static Connection createConnection() {
    // Use DRIVER and DBURL to create a connection
    // Recommend connection pool implementation/usage
  }
  public CustomerDAO getCustomerDAO() {
    // CloudscapeCustomerDAO implements CustomerDAO
    return new CloudscapeCustomerDAO();
  }
  public AccountDAO getAccountDAO() {
    // CloudscapeAccountDAO implements AccountDAO
    return new CloudscapeAccountDAO();
  }
  public OrderDAO getOrderDAO() {
    // CloudscapeOrderDAO implements OrderDAO
    return new CloudscapeOrderDAO();
  }
  ...
}

. Utilizar el Patrón Abstract Factory

Consideremos un ejemplo donde implementemos esta estrategia para tres bases de datos diferenets. En este caso, se puede emplear el patrón Abstract Factory. En la siguiente figura podemos ver el diagrama de clases para este ejmplo.

El siguiente fragmento de código muestra parte de la clase DAOFactory. Esta factoría produce DAOs como CustomerDAO, AccountDAO, OrderDAO, etc. Esta estrategia utiliza el patrón Factory Method en las factorías producidas por el Abstract Factory.

// Abstract class DAO Factory
public abstract class DAOFactory {

  // List of DAO types supported by the factory
  public static final int CLOUDSCAPE = 1;
  public static final int ORACLE = 2;
  public static final int SYBASE = 3;
  ...

  // There will be a method for each DAO that can be 
  // created. The concrete factories will have to 
  // implement these methods.
  public abstract CustomerDAO getCustomerDAO();
  public abstract AccountDAO getAccountDAO();
  public abstract OrderDAO getOrderDAO();
  ...

  public static DAOFactory getDAOFactory(
      int whichFactory) {
  
    switch (whichFactory) {
      case CLOUDSCAPE: 
          return new CloudscapeDAOFactory();
      case ORACLE    : 
          return new OracleDAOFactory();      
      case SYBASE    : 
          return new SybaseDAOFactory();
      ...
      default           : 
          return null;
    }
  }
}

El ejemplo de código para CloudscapeDAOFactory se ve en el siguiente listado. La implementación para OracleDAOFactory y SybaseDAOFactory son similares excepto para especifidades de cada implementación, como el driver JDBC, la URL de la base de datos, y diferencias en la síntaxis SQL, si existe-

// Cloudscape concrete DAO Factory implementation
import java.sql.*;

public class CloudscapeDAOFactory extends DAOFactory {
  public static final String DRIVER=
    "COM.cloudscape.core.RmiJdbcDriver";
  public static final String DBURL=
    "jdbc:cloudscape:rmi://localhost:1099/CoreJ2EEDB";

  // method to create Cloudscape connections
  public static Connection createConnection() {
    // Use DRIVER and DBURL to create a connection
    // Recommend connection pool implementation/usage
  }
  public CustomerDAO getCustomerDAO() {
    // CloudscapeCustomerDAO implements CustomerDAO
    return new CloudscapeCustomerDAO();
  }
  public AccountDAO getAccountDAO() {
    // CloudscapeAccountDAO implements AccountDAO
    return new CloudscapeAccountDAO();
  }
  public OrderDAO getOrderDAO() {
    // CloudscapeOrderDAO implements OrderDAO
    return new CloudscapeOrderDAO();
  }
  ...
}

El interface CustomerDAO mostrado en el siguiente listado, define los métodos DAO para los objetos Customer persistentes que son implementados por todas las implementaciones de DAOs concretos como CloudscapeCustomerDAO, OracleCustomerDAO, y SybaseCustomerDAO. Similares, aunque no se listan aquí, son los interfaces AccountDAO y OrderDAO que definen los metodos DAO para los objetos de negocio Account y Order respectivamente.

// Interface that all CustomerDAOs must support
public interface CustomerDAO {
  public int insertCustomer(...);
  public boolean deleteCustomer(...);
  public Customer findCustomer(...);
  public boolean updateCustomer(...);
  public RowSet selectCustomersRS(...);
  public Collection selectCustomersTO(...);
  ...
}

CloudscapeCustomerDAO implementa CustomerDAO como se ven en el siguiente listado. La implementación de los otros DAOs, como CloudscapeOrderDAO, OracleCustomerDAO, OracleAccountDAO, etc. son similares:

 
// CloudscapeCustomerDAO implementation of the 
// CustomerDAO interface. This class can contain all
// Cloudscape specific code and SQL statements. 
// The client is thus shielded from knowing 
// these implementation details.

import java.sql.*;

public class CloudscapeCustomerDAO implements 
    CustomerDAO {
  
  public CloudscapeCustomerDAO() {
    // initialization 
  }

  // The following methods can use
  // CloudscapeDAOFactory.createConnection() 
  // to get a connection as required

  public int insertCustomer(...) {
    // Implement insert customer here.
    // Return newly created customer number
    // or a -1 on error
  }
  
  public boolean deleteCustomer(...) {
    // Implement delete customer here
    // Return true on success, false on failure
  }

  public Customer findCustomer(...) {
    // Implement find a customer here using supplied
    // argument values as search criteria
    // Return a Transfer Object if found,
    // return null on error or if not found
  }

  public boolean updateCustomer(...) {
    // implement update record here using data
    // from the customerData Transfer Object
    // Return true on success, false on failure or
    // error
  }

  public RowSet selectCustomersRS(...) {
    // implement search customers here using the
    // supplied criteria.
    // Return a RowSet. 
  }

  public Collection selectCustomersTO(...) {
    // implement search customers here using the
    // supplied criteria.
    // Alternatively, implement to return a Collection 
    // of Transfer Objects.
  }
  ...
}

La clase Transfer Object Customer del siguiente listado la utilizan los DAOs para enviar y recibir datos desde los clientes. La utilización del patrón Transfer Object se explica con más detalles en la página Transfer Object:

 

public class Customer implements java.io.Serializable {
  // member variables
  int CustomerNumber;
  String name;
  String streetAddress;
  String city;
  ...

  // getter and setter methods...
  ...
}

El siguiente ejemplo muestra la utilización del factoría de DAOs y del DAO, Si la implementación cambiara de Cloudscape a otro producto, el único cambio que tendríamos que hacer es que el método getDAOFactory() llame a la factoría adecuada.

...
// create the required DAO Factory
DAOFactory cloudscapeFactory =   
  DAOFactory.getDAOFactory(DAOFactory.DAOCLOUDSCAPE);

// Create a DAO
CustomerDAO custDAO = 
  cloudscapeFactory.getCustomerDAO();

// create a new customer
int newCustNo = custDAO.insertCustomer(...);

// Find a customer object. Get the Transfer Object.
Customer cust = custDAO.findCustomer(...);

// modify the values in the Transfer Object.
cust.setAddress(...);
cust.setEmail(...);
// update the customer object using the DAO
custDAO.updateCustomer(cust);

// delete a customer object
custDAO.deleteCustomer(...);
// select all customers in the same city 
Customer criteria=new Customer();
criteria.setCity("New York");
Collection customersList = 
  custDAO.selectCustomersTO(criteria);
// returns customersList - collection of Customer
// Transfer Objects. iterate through this collection to
// get values.

...

. Patrones Relacionados

  • Transfer Object
    Un DAO utiliza Transfer Objects para transportar los datos desde y hacia sus clientes.
  • Factory Method [GoF] y Abstract Factory [GoF]
    La Factoría para la estrategia Data Access Objects usa el patrón Factory Method para implementar las factorías concretas y sus productos (DAOs). Para añadir flexibilidad, se podría emplear el patrón Abstract Factory como se explica en la sección de estrategias.
  • Broker [POSA1]
    El patrón DAO está relacionado con el patrón Broker, que describe aproximaciones para desacoplar clientes y servidores en sistemas distribuidos. El patrón DAO aplica este patrón específicamente para desacoplar la capa de recursos de los clientes en otra capa, como las capas de negocio o presentación.

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.