Tema 2: Patrones de diseño para aplicaciones locales sin EJBs


En este tema se revisarán algunos de los patrones incluídos en el catálogo de Core J2EE Patterns apropiados para aplicaciones con arquitectura local (todos los componentes en la misma máquina virtual) y que no usan EJBs. Al igual que en dicho catálogo, dividiremos los patrones por capas y utilizaremos una plantilla estándar para documentar las características de cada patrón. Como no es posible abordar aquí con profundidad todos los patrones del catálogo, nos limitaremos a dar una aproximación básica a algunos de ellos. El objetivo de este tema es pues, disponer de una "paleta básica" de patrones para una aplicación J2EE. Se remite al lector a la referencia original de Core J2EE Patterns para el catálogo completo.

Es importante destacar de nuevo que en este tema, para reducir el ámbito de la discusión, únicamente se abordan patrones relacionados con aplicaciones locales sin EJB. Así, por ejemplo cuando un patrón sea aplicable únicamente en una aplicación distribuida aquí no lo trataremos. Cuando un patrón sea aplicable por ejemplo en aplicaciones con y sin EJB únicamente hablaremos de los aspectos que no tienen que ver con EJB. Los demás casos se tratarán en los temas siguientes.

2.1. Plantilla para la descripción de los patrones

No es suficiente con conocer qué hace un patrón de diseño, hay que saber ante qué circunstancias es apropiado o no su uso, los beneficios y potenciales problemas que conlleva emplearlo y las alternativas de implementación existentes. Por ello, en el catálogo de Core J2EE Patterns se utiliza una plantilla estándar para describir los patrones, que aquí seguiremos de manera aproximada y que incluye los siguientes elementos:

2.2. Patrones para la capa de integración

2.2.1. Encapsulando la persistencia de datos: Data Access Object

Problema

Supongamos que el código de acceso a los recursos de datos (típicamente bases de datos relacionales) está incluído dentro de clases que tienen además otras responsabilidades diferentes. Un caso clásico es la inclusión de código JDBC o etiquetas de JSTL en un JSP para acceder a una base de datos. Para mejorar la modularidad y la mantenibilidad del sistema, es recomendable encapsular la persistencia de datos en una capa separada

Aplicabilidad

Solución

Utilizar un patrón DAO (Data Access Object). El DAO encapsula el acceso a la/s fuente/s de datos (DataSources), de manera que los detalles son transparentes para el cliente. Típicamente el DAO trabaja con un ResultSet resultado de una operación JDBC ocultando este ResultSet al cliente. Aquí el cliente sería cualquier objeto de la aplicación que necesitara la persistencia de datos. La siguiente figura muestra el diagrama de clases de este patrón. Como se ve, proporciona las típicas operaciones CRUD (Create, Read, Update, Delete).

DAO

La comunicación de datos entre cliente y DAO se hace a través de objetos Java (que siguen el patrón Transfer Objects - 2.2.4). Así, por ejemplo, cuando se invoca a create, update o delete el cliente le pasa al DAO un Transfer Object con los datos del objeto con el que operar, y cuando se llama a read el DAO devuelve un Transfer Object.

En general, se usaría un DAO distinto para cada objeto del modelo de objetos de la aplicación. Así, si hay por ejemplo clientes y pedidos, habría un ClienteDAO y un PedidoDAO.

Estrategias

Las siguientes estrategias de implementación se pueden utilizar de manera simultánea y complementaria.
Las siguientes estrategias son alternativas posibles para que el DAO devuelva una lista de resultados (por ejemplo, buscar todos los clientes con una deuda superior a 1000 euros)

Consecuencias

Entre las consecuencias positivas:
En cuanto a posibles consecuencias negativas:

Patrones J2EE relacionados

2.2.2. Localizando servicios de manera eficiente: Service Locator

NOTA: aunque en Core J2EE patterns se incluye este patrón en la capa de negocio, lo discutimos aquí ya que también se hace uso de él en la capa de integración.

Problema

En cualquier aplicación J2EE habrá componentes que necesiten acceder a recursos o servicios como DataSources de JDBC o  colas JMS. Para usar estos recursos normalmente es necesario localizarlos primero a través de JNDI. Como los componentes que acceden a dichos recursos están dispersos por la aplicación, el código de localización también lo estará, lo que conlleva problemas de mantenimiento y legibilidad. Nuestro objetivo es proporcionar un "responsable centralizado" para localización de recursos JNDI.

Aplicabilidad

Solución

Utilizar el patrón service locator para implementar y encapsular la búsqueda de recursos. El service locator esconde al cliente las posibles complejidades de la búsqueda, proporciona un API uniforme y puede mantener una cache para evitar llamadas innecesarias a JNDI. Normalmente no hará falta más de un service locator para toda la aplicación, por lo que se puede utilizar para su implementación el patrón singleton del GoF.

La siguiente figura muestra el diagrama de clases del patrón:
Service Locator

Como se ve, el Service Locator (que es un singleton) recibe solicitudes de los clientes para localizar recursos (que aquí se denominan Targets para denotar de modo genérico tanto DataSources como colas JMS, EJBs... ). El Service Locator crea la primera vez un contexto inicial JNDI (InitialContext) y solicita el recurso a través de dicho contexto. Mantiene además una Cache con los recursos ya solicitados para evitar peticiones JNDI innecesarias.

Consecuencias

Las consecuencias positivas son:

Patrones J2EE relacionados

2.2.3. Persistencia transparente: Domain Store

Problema

Supongamos que hemos desarrollado un modelo orientado a objetos del dominio de la aplicación (por ejemplo: Cliente,  Factura, Item...) pero queremos olvidarnos de la implementación de la persistencia de datos. Una alternativa sería usar EJBs de entidad con persistencia manejada por el contenedor, pero el uso de EJBs impone restricciones en la forma de programar dichos objetos (por ejemplo, en cuanto a herencia). Lo mejor sería tener persistencia transparente: es decir, que el hecho de ser objetos persistentes no interfiriera con el diseño del modelo de objetos.
Aplicabilidad

Solución

Usar el patrón domain store para conseguir persistencia transparente del modelo de objetos. El diagrama de clases de dicho patrón se muestra en la figura siguiente:



Como se ve, Domain Store es un "macro-patrón" que implica la interacción de multitud de clases. De hecho, es de los patrones más complejos descritos en el catálogo, por lo que aquí solo podemos tratarlo de manera muy resumida. La clase central del patrón es el PersistenceManager, que es el encargado de gestionar la persistencia de los objetos deseados. Al PersistenceManager se le puede pedir que haga persistente un objeto (con lo que a partir de ese momento, todos los cambios hechos en él quedarán reflejados en la base de datos), que lo borre o bien se pueden ejecutar consultas. Internamente, el PersistenceManager realiza todas estas operaciones accediendo a las fuentes de datos a través de un DAO (2.2.1). Cada clase tiene un StateManager que es el encargado de la persistencia de esta clase en particular, y en el que delega el PersistenceManager. Para que un objeto (Business Object) sea persistente debe estar registrado con un StateManager e implementar la interfaz Persistable. Finalmente, el documento XML PersistMap contiene la correspondencia entre campos de los objetos y columnas de las tablas de la base de datos.

Estrategias

La implementación de Domain Store puede ser una tarea muy compleja salvo que se disponga de herramientas de mapeado objetos-relacional ya desarrolladas. Implementar este patrón partiendo de cero equivale a desarrollar nuestra propia herramienta de mapeado O-R, lo cual puede no compensar el esfuerzo en muchos casos. Una alternativa más sencilla consiste en utilizar un API al estilo de JDO (Java Data Objects) que proporcione los servicios del patrón. JDO proporciona persistencia automática de objetos implementando un esquema muy similar al diagrama de clases anterior. Por ejemplo,  la clase PersistenceManager de JDO ofrece una funcionalidad prácticamente idéntica a la mostrada en el diagrama.

Consecuencias

Entre las positivas:
Entre las negativas:

2.2.4. Transfiriendo datos: Data Transfer Object

NOTAS:

Problema

En muchos casos es necesario transferir datos entre componentes de una aplicación J2EE. Por ejemplo, para crear un cliente en la base de datos será necesario pasar todos sus campos al DAO (nombre, dirección, crédito,...). Aunque la información se puede pasar campo a campo, es más natural pasarla como un único objeto.

Solución

Utilizar el patrón Transfer Object para transferir datos entre componentes de la aplicación. En su forma más simple, un Transfer Object es un objeto Java que encapsula una serie de datos al estilo JavaBean. En aplicaciones distribuidas, el Transfer Object tiene también implicaciones positivas en la eficiencia. El esquema de clases de este patrón es bastante sencillo.

Diagrama de clases de Transfer Object

Un componente cualquiera de una aplicación J2EE necesita enviar/recibir información a/desde otro componente. Dicho componente puede estar en cualquier capa de la aplicación (presentación, negocio o integración). El envío/recepción de información se encapsula en un TransferObject, que no es más que un objeto Java común (en el argot, un POJO).

Estrategias

Los dos siguientes puntos son consideraciones prácticas de implementación más que alternativas:

Consecuencias

Positivas:
Negativas:

Patrones J2EE relacionados

2.3. Patrones para la capa de negocio

2.3.1 Modelando el dominio: Business Object

Problema

El punto central de cualquier metodología de diseño y desarrollo orientado a objetos es construir un modelo de objetos que refleje adecuadamente el dominio de la aplicación. En la práctica este requisito puede obviarse si la aplicación es sencilla, ya que los clientes pueden acceder directamente a las bases de datos, por ejemplo a través de DAOs. En este caso la propia base de datos es el reflejo más directo del "modelo del dominio" (aunque no sea orientado a objetos) y la aplicación acaba siendo en realidad más procedural que orientada a objetos (por mucho que se implemente en Java). No obstante, conforme la lógica de negocio se va haciendo más compleja, es más conveniente mantener un modelo orientado a objetos del dominio.

Aplicabilidad

Solución

Utilizar business objects para construir un modelo orientado a objetos del dominio.  Un business object debería ser un objeto reutilizable, que incluya la lógica de negocio propia del concepto del dominio que modela. La lógica de negocio que implique a varios objetos distintos debería implementarse fuera del business object (utilizando por ejemplo un patrón Application Service - 2.3.2).

La siguiente figura muestra el diagrama de clases del patrón.

diagrama de clases de Business Object


Como se ve en el diagrama, un Business Object puede estar compuesto de objetos dependientes (DependentBO). Un objeto dependiente es aquel que no tiene sentido por sí mismo y siempre debe estar vinculado a un objeto "padre" (ParentBO). Un ejemplo típico sería un objeto Pedido del que dependen varios ItemPedido. Las demás clases de la aplicación pueden acceder directamente al objeto padre, pero solo indirectamente a los dependientes. Las clases que acceden a un Business Object pueden ser Helpers (clases POJO utilizadas por la capa de presentación para acceder a  la capa de negocio), Application  Services (2.3.2 - que encapsulan la lógica de negocio que implica a varios Business Objects) o Service Facades (puntos de entrada a la capa de negocio que proporcionan un API con los servicios que esta da de cara al exterior).

Estrategias

Una de las decisiones fundamentales que hay que tomar al implementar Business Objects es si se utilizarán POJOs o Entity Beans. En principio es mucho más sencillo implementar el modelo del dominio con POJOs, pero hay que tener en cuenta que en este caso habrá también que implementar una serie de servicios que el servidor de aplicaciones proporciona automáticamente con los EJBs (manejo de transacciones, seguridad, persistencia automática, integridad y consistencia de datos...). Una alternativa al uso de Entity Beans es utilizar una capa de Session Beans que proporcionen algunos de estos servicios (transacciones y seguridad, por ejemplo) y acceder a través de ellos a los POJOs con el modelo del dominio.

Consecuencias

entre las positivas:
entre las negativas:

Patrones J2EE relacionados

2.3.2 Centralizando la lógica de negocio: Application Service

Problema

Muchos casos de uso implican a varias clases del modelo de dominio (Business Objects - 2.3.1). La lógica de negocio asociada a esos casos de uso no debería implementarse dentro de dichas clases ya que no es propia de una de ellas en concreto, sino de varias. Sería interesante centralizar en algún punto dicha lógica de negocio para proporcionar una capa de servicios.

Aplicabilidad

Solución

Utilizar el patrón Application Service para centralizar la lógica de negocio común a varios objetos del modelo del dominio. Al sacar esta lógica fuera de los objetos del modelo se reduce el acoplamiento entre dichos objetos. Aunque no se disponga de un modelo explícito del dominio en forma de Business Objects (2.3.1) el Application Service puede acceder directamente a las fuentes de datos a través de Data Access Objects (2.2.1).

Si en la aplicación hay lógica de negocio que depende del tipo de cliente (por ejemplo, el procesamiento es distinto si accede un navegador o un cliente Swing), un Application Service constituye un punto apropiado para encapsular también dicha lógica. Así, los objetos del modelo de dominio encapsularían la lógica común a todos los clientes mientras que el Application Service tendría la propia de cada tipo.

La siguiente figura muestra el diagrama de clases de este patrón.

Diagrama de clases de Application Service

El Application Service accede a los objetos del modelo del dominio (Business Objects - 2.3.1) o bien en aplicaciones más simples directamente a los Data Access Objects (2.2.1). También puede necesitar el acceso a servicios externos (Service), por ejemplo, envío de e-mail. Los clientes del Application Service son Helpers (clases POJO utilizadas por la capa de presentación para acceder a  la capa de negocio) o Service Facades (puntos de entrada a la capa de negocio que proporcionan un API con los servicios que esta da de cara al exterior).

Consecuencias

Entre las positivas:
Como consecuencia negativa:

Patrones J2EE relacionados


2.3.3 Aislando a los clientes de la capa de negocio: Business Delegate

Problema

Si las clases que están en la capa de presentación acceden directamente a las que implementan los servicios de la capa de negocio, se pueden plantear varios tipos de problemas. Por ejemplo, si el código de la capa de negocio cambia, puede ser necesario cambiar también los clientes: si la capa de negocio pasa de estar basada en POJOs a estar basada en EJBs habrá que cambiar el código que emplean los clientes para acceder a los objetos de negocio. Además, el cliente debe enfrentarse con los errores que devuelva la capa de negocio (y que puede no saber cómo tratar). Sería más conveniente encapsular la comunicación entre la capa de presentación y la de negocio en alguna clase que se ocupe de estos problemas.

Aplicabilidad

Solución

Utilizar el patrón Business Delegate para que sirva de puente entre la capa de presentación y la de negocio. El Business Delegate abstrae a los clientes de los posibles cambios en la implementación de la capa de negocio y gestiona las excepciones que esta pueda generar, reintentando las operaciones o transformando la excepción en otra que tenga significado para la capa de presentación.

NOTA: Aunque el Business Delegate residiría "físicamente" en la capa de presentación, desde el punto de vista lógico es la entrada a la capa de negocio, por lo que su discusión se ha encuadrado en esta capa. No obstante, esta distinción entre "residencia física" y "lógica" no es significativa en aplicaciones locales, pero sí en remotas. En ese último caso el Business Delegate accede a los servicios de la capa de negocio como servicios remotos.

La siguiente figura muestra el diagrama de clases del patrón.



Como se ve, el Business Delegate es una clase común de Java (un POJO) que proporciona acceso a los servicios de la capa de negocio (BusinessService). Podría necesitar el uso de un Service Locator (2.2.2) en caso de tener que acceder a servicios remotos en aplicaciones distribuidas.

Consecuencias

Entre las positivas:
La principal consecuencia negativa:

Patrones J2EE relacionados



2.4. Patrones para la capa de presentación

2.4.1. Separando la lógica de la vista: View helper

Problema

En algunos casos podemos tener lógica de control mezclada con la vista. Esto es típico de los JSPs en los que se tiende a introducir código Java y se acaba mezclando la presentación de los datos con bucles, condicionales, etc. Este enfoque causa problemas de mantenibilidad y reusabilidad, ya que en principio la parte de presentación la deben trabajar los diseñadores mientras que la de la lógica es competencia de los programadores.

Aplicabilidad

Solución

Utilizar el patrón View Helper para separar la lógica de la vista. Un View Helper delegará en otras clases para hacer la tarea que se le encomiende y luego le pasará a la vista los datos en el formato deseado por ella. Las clases en las que delegue el Helper dependerán de la tarea: la lógica de negocio será encomendada finalmente a Business Objects (2.3.1.), la de control a un Front Controller (ver patrón Service to Worker - 2.4.2.) y la de acceso a datos a los Data Access Object (2.2.1).

Estrategias

La implementación del patrón se puede hacer de distintas maneras: una posibilidad es usar una librería de tags propia que encapsulen la lógica necesaria. Otra posibilidad no tan sofisticada es hacer uso de JavaBeans.

Patrones relacionados


2.4.2. Aplicando MVC: Service to Worker y Dispatcher View

NOTA: ambos patrones J2EE son en realidad variantes del patrón genérico MVC (Modelo-Vista-Controlador) que ya se trató ampliamente en el curso dentro del bloque de JSP. Por ello aquí se discuten ambos de manera unificada y muy resumida. Para una referencia más detallada, acudir al catálogo de Core J2EE Patterns.

Problema

Supongamos que en una aplicación se tiene la lógica de control mezclada con la vista, de modo que está dispersa (tipicamente dividida en varios JSPs) y además parte de ella se encuentra repetida en varias vistas. La aplicación de la filosofía MVC (Modelo-Vista-Controlador) permite la separación clara entre el modelo (la capa de negocio), la vista, y la lógica de control.

Solución

Aplicar el patrón Service to Worker o bien el Dispatcher View para conseguir una división entre lógica de control, vista y modelo. Ambos patrones son en realidad "macro-patrones" que están formados por el trabajo conjunto de varios otros. La diferencia entre ambos "macro-patrones" consiste básicamente en cuándo se ejecuta el acceso al modelo y quién lo hace. En el caso de Service to Worker, el acceso al modelo y la recopilación de los datos se realiza antes de que se pase el control a la vista, que únicamente es responsable de su presentación. En Dispatcher View, la vista es la que accede al modelo para recuperar los datos.

El framework Struts, que se trató en el curso dentro del bloque de JSP, constituye una implementación del patrón Service to Worker.