Value List Handler
Contexto
El cliente le pide al servicio una lista de ítems para su presentación. El
número de ítems de la lista no se conoce y puede ser muy grande en muchas
circunstancias.
Problema
La mayoría de las aplicaciones de la plataforma J2EE tienen un requerimiento de
búsqueda y consulta para buscar y listar ciertos datos. En algunos casos, dichas
operaciones de búsqueda y consulta podrían traer resultados que pueden ser
bastante grandes. No es práctico devolver toda la hoja de resultados cuando los
requerimientos del cliente son moverese por los resultados, en vez de procesar el
conjunto completo. Normalmente, un cliente usa los resultados de una consulta
para propósitos de sólo-lectura, como mostrar una lista de resultados. Muchas
veces el cliente sólo ve los primeros registros que devuelve la búsqueda, y
podría descargar los registros restantes e intentar otra nueva consulta. La
práctica de obtener uns lista de valores representados en un bean de entidad
llamando al método ejbFind(), que devuelve una
collection de objetos remotos, y luego llamar a
cada bean de entidad para obtener el valor, consume muchos recursos de red y se
considera una mala práctica.
Hay algunas consecuencias asociadas con el uso de los métodos búscadores de EJB
que resultan en grandes conjuntos de datos. Cada implementación de contenedor
tiene una cierta cantidad de métodos búscadores para crear una colección de
referencias EJBObject. El rendimiento del método
de búsqueda varía, dependiendo de la implementación del contenedor. De acuerdo a
la especificación EJB, un contenedor podría invocar a métodos
ejbActivate() sobre entidades encontradas con los métodos de búsqueda. Como mínimo, un método de búsqueda devuelve las claves primarias de las entidades encontradas, que el contenedor devuelve al cliente como una colección de referencias
EJBObject. Este comportamiento se aplica para todas las implementaciones de contenedores. Algunas implementaciones podrían introducir una sobrecarga de métodos de búsqueda asociando los ejemplares de beans de entidad a esos ejemplares
EJBObject para darle acceso al cliente a esos
beans de entidad. Sin embargo, este es un uso pobre de los recursos si el cliente no está interesado en acceder al bean o en invocar sus métodos. Esta sobrecarga puede impedir el
rendimiento de la aplicación si ésta incluye consultas que producen muchos resultados.
Causas
- La aplicación cliente necesita una facilidad de consulta eficiente para
evitar tener que llamar al método ejbFind() de
cada bean e
invocar a cada
objeto remoto devuelto.
- Se necesita una mecanismo de caché en la capa-servidor para servir a los
clientes que no pueden recibir y procesar el cojunto de resultados
completo.
- Se puede optimizar una consulta que se ejecuta repetidamente sobre unos
datos razonablemente estáticos para proporcionar resultados más rápidos. Esto
depende de la aplicación y de la implementación de este patrón.
- Los métodos de búsqueda EJB no son apropiados para navegar por tablas
enteras en la bases de datos o para buscar grandes conjuntos de resultados en
una tabla.
- Los métodos de búsqueda se podrían considerar sobrecargados cuando se
utilizan para encontrar grandes cantidades de resultados. El contenedor podría
crear un gran número de objetos de infraestructura para facilitar las
búsquedas.
- Los métodos de búsqueda EJB no sin apropiados para realizar caché de
resultados. El cliente podría no ser capaz de manejar el conjunto completo de
resultados en una sóla llamada. Si es así, el cliente podría necesitar un caché
en el lado del cliente y funciones de navegación para moverese por el conjunto
de resultados.
- Los métodos de búsqueda EJB tiene consultas predeterminadas y ofrecen una
flexibilidad mínima. La especificación EJB 2.0 permite un lenguaje de consulta,
EJB QL, para beans de entidad manejados por el contenedor. EJB QL hace más fácil
escribir métodos de búsqueda portables y ofrece una mayor flexibilidad.
- El cliente quiere moverse hacia adelante o hacia atrás dentro de la hoja de
resultados.
Solución
Utilizar un Value List Handler para controlar la búsqueda, hacer un
caché
con los resultados, y proporcionar los resultados al cliente en una hoja de
resultados cuyo tamaño y desplazamiento cumpla los requerimientos del
cliente.
Este patrón crea un ValueListHandler para
controlar la
funcionalidad de
la ejecución de la consulta y el caché de resultados. El
ValueListHandler
accede directamente a un DAO que puede ejecutar la consulta requerida. El
ValueListHandler almacena los resultados
obtenidos del DAO
como una
colección de Transfer Objects. El cliente le pide al
ValueListHandler que proporcione resultados de la
consulta
según los
necesite. El ValueListHandler implementa un
patrón
Iterator
[GoF] para proporcionar la solución.
Estructura
La siguiente figura muestra el diagrama de clases para el patrón Value
List
Handler:
Participantes y Colaboraciones
La siguiente figura muestra el diagrama de secuencia del patrón Value
List
Handler:
ValueListIterator
Este interface podría proporcionar la facilidad de iteración con los siguientes
métodos de ejemplo:
- getSize() obtiene el tamaño de la hoja de
resultados.
- getCurrentElement() obtiene el
Transfer
Object
actual
de la
lista.
- getPreviousElements(int howMany) obtiene una
colección de
Transfer
Objects que son anteriores en la lista al elemento actual.
- getNextElements(int howMany) obtiene una
colección de
Transfer
Objects que son posteriores en la lista al elemento actual.
- resetIndex() reinicia el índice para ir al
inicio de la
lista.
Dependiendo de las necesidades, se pueden incluir otros métodos de conveniencia
en el interface ValueListIterator.
ValueListHandler
Este es el objeto que implementa el interface
ValueListIterator.
ValueListHandler ejecuta la consulta requerida
cuando se lo
solicita el
cliente. Obtiene los resultados de la consulta, que maneja en una colección
privada representada por el objeto ValueList.
ValueListHandler
crea y manipula la colección ValueList. Cuando el
cliente
solicita los
resultados, ValueListHandler obtiene los
Transfer
Objects
desde el
ValueList cacheado, crea una nueva colección de
Transfer
Objects,
serializa la colección y la envía de vuelta alcliente.
ValueListHandler
también sigue la pista del índice actual y del tamaño de la lista.
DataAccessObject
ValueListHandler puede hacer uso de un
DataAccessObject para
mantener separada la implementación del acceso a la base de datos.
DataAccessObject proporciona un API sencillo para
acceder a
la base de
datos (o cualquier otro almacenamiento persistente), ejecuta consultas y
recupera resultados.
ValueList
ValueList es una colección (una lista) que
contiene los
resultados de la
consulta. Los resultados se almacenan como objetos Transfer
Objects. Si
falla la consulta y no se devuelven resultados, entonces esta lista está vacía.
El bean de sesión ValueListHandler puede
almacenar
ValueList para
evitar repeticiones innecesarias de la consulta.
TransferObject
TransferObject representa una vista de un
registro individual
de los
resultados de la consulta. Es un objeto serializable no modificable que
proporciona un espacio para los datos de los atributos de cada registro.
Estrategias
Java Object
Se puede implementar el ValueListHandler como un
objeto Java
arbitario.
En este caso, el ValueListHandler puede ser
utilizado por
cualquier
cliente que necesite esa funcionalidad. Para aplicaciones que no usan beans
enterprise, esta estrategia es muy útil. Por ejemplo se podría construir
aplicaciones sencillas utilizando servlets, JSPs, Business
Delegates, y
DAOs. En este escenario, el Business Delegates puede
utilizar un
ValueListHandler implementado como un objeto Java
para
obtener una lista
de valores.
Stateful Session Bean
Cuando una aplicación utiliza beans enterprise en la capa de negocio, se podría
preferir implementar un bean de sesión que utilice el
ValueListHandler.
En este caso, el bean de sesión simplemente se enfrenta a un ejemplar de
ValueListHandler. Así, el bean de sesión se
podría
implementar como un
bean de sesión con estado para contener la lista y su estado, y esto simplemente
podría actúar como una fachada o como un proxy.
Consecuencias
- Proporciona Alternativas a los métodos
find()
de EJB para
Grandes
Consultas
Normalmente, un método de búsqueda EJB es una forma cara de obtener una lista de
ítems, ya que implica un número de referencias
EJBObject.
Value
List
Handler implementa un bean de sesión que utiliza un DAO para realizar
la
consulta y para proporcionar una colección de Transfer
Objects que
cumplen el criterio de búsqueda. Como los Transfer Objects
tienen
poca
sobrecarga en comparación con las referencias
EJBObject y su
infraestructura asociada, este patrón proporciona beneficios cuando las
aplicaciones clientes requieren grandes cantidades de resultados.
- Crea un Caché de Resultados de la Consulta en el Lado del
Servidor
Se necesita cachear los resultados obtenidos de la ejecución de la consulta
cuando un cliente debe mostrarlos en pequeñas partes en vez de una gran lista.
Sin embargo, no todos los clientes basados en navegador puede realizar dicho
caché. Cuando no pueden, el servidor debe proporcionar esta funcioalidad. El
patrón Value List Handler proporciona una facilidad de
caché en el
bean
de sesión para contener los resultados de la consulta. El conjunto de resultados
es una colección de Transfer Objects que se puede
serializar si es
necesario.
Cuando el cliente solicita una colección, o un subconjunto de una colección, el
baen manejador devuelve los resultados solicitados como una colección
serializada de Transfer Objects. El cliente recibe la
colección y
ahora
tiene una copia local de la información, que el cliente puede mostrar o
procesar. Cuando el cliente necesita un subconjunto adicional de resultados, le
solicita al manejador que devuelva otra colección serializada que contenga los
resultados necesarios. El cliente puede procesar la consulta en trozos más
pequeños y manejables. El bean manejador también le proporciona al cliente
facilidades de navegación (anterior y siguiente) para que pueda moverse por la
lista de la forma necesaria.
- Proporciona una Mayor Flexibilidad de Consulta
Añadir una nueva consulta podría requerir la creación de un nuevo método
finder o modificar uno existente, especialmente
cuando se
utilizan beans
de entidad manejados por el bean (con beans de entidad manejados por el bean, el
desarrollador implementa los métodos de búsqueda en la implementación del bean).
Con un bean de entidad manejado por el contenedor, el desarrollador especifica
los métodos finder del bean de entidad en el
descriptor de
despliegue del
bean. Los cambios en una consulta para bean manejado por el contenedor requieren
cambios en el descriptor de despliegue. Por lo tanto, estos métodos no son muy
adecuados cuando los requerimientos de la consulta cambian dinámicamente.
Podemos
implementar un Value List Handler para que sea más flexible
que
los
métodos del EJB proporcionado facilidades de consultas dinámicas, construyendo
los argumentos de la consulta en el momento de la ejecución utilizando
plantillas de métodos, etc. En otras palabras, un desarrollador de un
Value
List Handler puede implementar búsqueda inteligente y algoritmos de
caché
sin verse limitado por los métodos finder.
- Mejora el Rendimiento de Red
El rendimiento de la red se ve mejorado porque sólo los datos solicitados, en
vez de todos los datos, se envían (serializados) al cliente sobre unas
necesidades básicas. Si el cliente muestra los primeros resultados y luego
abandona la consulta, no se ha ocupado el ancho de banda de la red, ya que los
datos se almacenan en el servidor y nunca se han envíado al cliente. Sin
embargo, si el cliente procesa todos los resultados, hace varias llamadas
remotas al servidor. Cuando el cliente conoce por adelantado que necesita todos
los resultados, el bean manejador puede proporcionar un método que los envíe al
cliente en una sola llamada a método, y no se utiliza el caché.
- Permite Atrasar las Transaciones del Bean de Entidad
El caché de los resultados en el lado del servidor permite minimizar la
sobrecarga de la búsqueda y podría mejorar el control de transaciones. Cuando el
cliente está listo para procesar un bean de entidad, accede al bean dentro de
un contexto de transación definido por el caso de utilización. Por ejemplo, una
consulta para mostrar una lista de libros utiliza un Value List
Handler
para obtener la lista. Cuando el usuario quiere ver los detalles de un libro,
implica al bean de entidad del libro en la transación.
Código de Ejemplo
Implementar el Value List Handler como un objeto Java
Consideremos un ejemplo donde se va a recuperar y mostrar una lista de objetos
de negocio de un Proyecto. En este caso se puede aplicar el patrón
Value List
Handler. En el siguiente ejemplo tenemos la clase
ProjectListHandler,
que es responsable de proporcionar la lista de proyectos. Esta clase extiende la
clase base ValueListHandler, que proporciona la
funcionalidad
de
iteración genéricoa para todas las implementaciones de Value List
Handler
de esta aplicación.
package corepatterns.apps.psa.handlers;
import java.util.*;
import corepatterns.apps.psa.dao.*;
import corepatterns.apps.psa.util.*;
import corepatterns.apps.psa.core.*;
public class ProjectListHandler
extends ValueListHandler {
private ProjectDAO dao = null;
// use ProjectTO as a template to determine
// search criteria
private ProjectTO projectCriteria = null;
// Client creates a ProjectTO instance, sets the
// values to use for search criteria and passes
// the ProjectTO instance as projectCriteria
// to the constructor and to setCriteria() method
public ProjectListHandler(ProjectTO projectCriteria)
throws ProjectException, ListHandlerException {
try {
this.projectCriteria = projectCriteria;
this.dao = PSADAOFactory.getProjectDAO();
executeSearch();
} catch (Exception e) {
// Handle exception, throw ListHandlerException
}
}
public void setCriteria(ProjectTO projectCriteria) {
this.projectCriteria = projectCriteria;
}
// executes search. Client can invoke this
// provided that the search criteria has been
// properly set. Used to perform search to refresh
// the list with the latest data.
public void executeSearch()
throws ListHandlerException {
try {
if (projectCriteria == null) {
throw new ListHandlerException(
"Project Criteria required...");
}
List resultsList =
dao.executeSelect(projectCriteria);
setList(resultsList);
} catch (Exception e) {
// Handle exception, throw ListHandlerException
}
}
}
En el siguiente código tenemos la clase
ValueListHandler que
implementa
el interface del iterador genérico
ValueListIterator, que
veremos en
siguientes listados.
package corepatterns.apps.psa.util;
import java.util.*;
public class ValueListHandler
implements ValueListIterator {
protected List list;
protected ListIterator listIterator;
public ValueListHandler() {
}
protected void setList(List list)
throws IteratorException {
this.list = list;
if(list != null)
listIterator = list.listIterator();
else
throw new IteratorException("List empty");
}
public Collection getList(){
return list;
}
public int getSize() throws IteratorException{
int size = 0;
if (list != null)
size = list.size();
else
throw new IteratorException(...); //No Data
return size;
}
public Object getCurrentElement()
throws IteratorException {
Object obj = null;
// Will not advance iterator
if (list != null)
{
int currIndex = listIterator.nextIndex();
obj = list.get(currIndex);
}
else
throw new IteratorException(...);
return obj;
}
public List getPreviousElements(int count)
throws IteratorException {
int i = 0;
Object object = null;
LinkedList list = new LinkedList();
if (listIterator != null) {
while (listIterator.hasPrevious() && (i < count)){
object = listIterator.previous();
list.add(object);
i++;
}
}// end if
else
throw new IteratorException(...); // No data
return list;
}
public List getNextElements(int count)
throws IteratorException {
int i = 0;
Object object = null;
LinkedList list = new LinkedList();
if(listIterator != null){
while( listIterator.hasNext() && (i < count) ){
object = listIterator.next();
list.add(object);
i++;
}
} / / end if
else
throw new IteratorException(...); // No data
return list;
}
public void resetIndex() throws IteratorException{
if(listIterator != null){
listIterator = list.ListIterator();
}
else
throw new IteratorException(...); // No data
}
...
}
Abajo tenemos el código importante para acceder al objeto
ProjectDAO,
utilizado por ValueListHandler para ejecutar la
consulta y
obtener los
resultados.
package corepatterns.apps.psa.dao;
public class ProjectDAO {
final private String tableName = "PROJECT";
// select statement uses fields
final private String fields = "project_id, name," +
"project_manager_id, start_date, end_date, " +
" started, completed, accepted, acceptedDate," +
" customer_id, description, status";
// the methods relevant to the ValueListHandler
// are shown here.
// See Data Access Object pattern for other details.
...
private List executeSelect(ProjectTO projCriteria)
throws SQLException {
Statement stmt= null;
List list = null;
Connection con = getConnection();
StringBuffer selectStatement = new StringBuffer();
selectStatement.append("SELECT "+ fields +
" FROM " + tableName + "where 1=1");
// append additional conditions to where clause
// depending on the values specified in
// projCriteria
if (projCriteria.projectId != null) {
selectStatement.append (" AND PROJECT_ID = '" +
projCriteria.projectId + "'");
}
// check and add other fields to where clause
...
try {
stmt = con.prepareStatement(selectStatement);
stmt.setString(1, resourceID);
ResultSet rs = stmt.executeQuery();
list = prepareResult(rs);
stmt.close();
}
finally {
con.close();
}
return list;
}
private List prepareResult(ResultSet rs)
throws SQLException {
ArrayList list = new ArrayList();
while(rs.next()) {
int i = 1;
ProjectTO proj = new
ProjectTO(rs.getString(i++));
proj.projectName = rs.getString(i++);
proj.managerId = rs.getString(i++);
proj.startDate = rs.getDate(i++);
proj.endDate = rs.getDate(i++);
proj.started = rs.getBoolean(i++);
proj.completed = rs.getBoolean(i++);
proj.accepted = rs.getBoolean(i++);
proj.acceptedDate = rs.getDate(i++);
proj.customerId = rs.getString(i++);
proj.projectDescription = rs.getString(i++);
proj.projectStatus = rs.getString(i++);
list.add(proj);
}
return list;
}
...
}
Código de ejemplo de la clase ValueListIterator:
package corepatterns.apps.psa.util;
import java.util.List;
public interface ValueListIterator {
public int getSize()
throws IteratorException;
public Object getCurrentElement()
throws IteratorException;
public List getPreviousElements(int count)
throws IteratorException;
public List getNextElements(int count)
throws IteratorException;
public void resetIndex()
throws IteratorException;
// other common methods as required
...
}
Patrones Relacionados
- Iterator [GoF]
El patrón Value List Handler está basado en el patrón
Iterator,
descrito en el libro de Gof: Design Patterns:
Elements
of
Reusable
Object-Oriented Software.
- Session Facade
Como el Value List Handler es un bean de sesión, podría
aparecer
como un
Session Facade especializado. Sin embargo, aisladamente, es
un
bean de
sesión especializado en vez de un Session Facade. Un
Session
Facade tiene otras motivaciones y características y es mucho más
genérico.