jueves, 26 de noviembre de 2009

Hibernate Versioning - Consideraciones

Escenario:
_ Estrategia
session-per-request para manejo de sesiones.
_ Objetos detached manipulados en casos de negocio arbitrariamente largos.

Objetivo
:
_ Control de versiones automático por parte de Hibernate.

Caso
:
Considerar como ejemplo una clase Cliente, que necesita ser controlada para sincronización.
Dada la estrategia de uso de sesiones, es necesario utilizar un campo adicional en la tabla cliente para ingresar un número de versión.
_ Agregar un campo
int a la tabla, y agregar el atributo a la clase Cliente.

Configuración del mapping xml:
_ En Cliente.hbm.xml agregar el mapping para version, luego del tag <id>:
<version name="version" column="version" />

El comportamiento por defecto de Hibernate es realizar control de versiones a través de un campo en la tabla, pero de todas maneras se puede especificar, en el tag <class>:
optimistic-lock="version"

Manejar los errores de versión desde la aplicación
Cuando se intenta asociar una instancia de la clase Cliente a una Session nueva, por ejemplo a través de un update(), Hibernate lanzará una StaleObjectStateException.
El código para manejarlo puede ser el siguiente:

protected boolean update(PersistentObject objeto){
___Transaction
tx = getHibernateSession().beginTransaction();
___try {
______getHibernateSession().update(objeto);
______tx.commit(); // Aqui se produce la excepción
______closeSession();
______return true;
___}
___catch(StaleObjectStateException e) {
______tx.rollback();
______closeSession();
______return false;
___}
}

En una capa superior de la aplicación, se puede utilizar el resultado de esta función y algunos chequeos para saber si la excepción se produjo porque la instancia fue modificada o eliminada con anterioridad:

...
boolean valid = update(cliente);
if (valid) {
___// Update OK
___// <Código si update exitoso>
}
else{ // No se pudo actualizar el elemento
___// Verifico si fue modificado o eliminado
___// Intenta cargar el cliente de nuevo
___Cliente clienteRel = sess.load(Cliente.class, cliente.getIdCliente());
___if (clienteRel == null){ // El cliente fue borrado antes
______err_mesg = "El cliente fue borrado por otra aplicacion";
___}
___else{ // El cliente fue modificado antes
______err_mesg = "El cliente fue modificado por otra aplicacion";
___}
}
...


Aquí lo que obtenemos es la diferenciación de las distintas situaciones, y un mensaje de error apropiado para cada una.

miércoles, 14 de octubre de 2009

Manejo de versiones o 'versioning' con Hibernate

El manejo automático de versiones es una técnica simple para asegurar la integridad de datos.
Rápidamente podemos pensar en un ejemplo en donde tenemos 2 clientes A y B que cargan el mismo registro R. Luego de un tiempo A hace 'commit' de R con los datos cambiados, y después B hace lo mismo, cambiando los datos de R, pero no desde el estado de R luego del 'commit' de A.

Lo que hace el manejo automático de versiones es mantener un campo para versionar el registro. En el ejemplo anterior, A y B obtienen el registro R con una versión V. Luego del 'commit' de A la versión se cambia, y de esta forma cuando B intenta hacer lo mismo, las versiones son distintas y el chequeo de esta versión falla.

En Hibernate podemos declarar el campo de versión y se recomienda que sea del tipo int o long.
Aquí vemos un ejemplo en donde asumimos que el atributo se llama 'version' y lo queremos mapear a la columna 'version' en la base de datos:

<version name="version" column="version"/>

En general con Hibernate surgen dudas sobre cómo la aplicación se da cuenta de que un objeto ha cambiado en la base de datos.

El funcionamiento es el siguiente: Al usar Hibernate se deberia llamar a saveOrUpdate() o update() cuando se quiere actualizar un dato cambiado. Estos métodos lo que hacen es realizar una consulta del estilo:
UPDATE Persona SET nombre='juan', VERSION=2
WHERE ID=123 AND VERSION=1

Luego, Hibernate chequea la cantidad de filas afectadas en el resultado de JDBC, y lanza una excepción del tipo StaleObjectStateException si ninguna fila fue actualizada. De esta forma, podemos atrapar dicha excepción y darnos cuenta de que hubo un conflcto.
Algo importante a notar es que cada actualización incrementa el número de versión en uno y chequea que no haya sido cambiado desde que fue leído.

martes, 6 de octubre de 2009

Cadena de Responsabilidad (Chain of Responsibility)

Este patrón tiene como propósito establecer una cadena dentro del sistema, de tal forma que un mensaje puede ser manejado en el nivel donde es recibido por primera vez, o puede ser reenviado hacia un objeto que pueda manejarlo.

Cuando alguna acción se produce dentro de un sistema orientado a objetos, es común que se represente a través de un evento o mensaje.

En los casos más simples, el mismo objeto que produce el mensaje responde a él. Por ejemplo, un campo de texto puede producir eventos en respuesta a acciones del usuario (como escribir con el teclado) y también puede responder a esos eventos(mostrando texto en el campo).

En casos más complejos, la respuesta a los mensajes puede tener un papel más importante.

La Cadena de Responsabilidades es una cadena de reenvío de mensajes. Si un objeto no puede manejar un mensaje dado, lo pasa hacia otro objeto más arriba. Frecuentemente la cadena se implementa con un modelo padre-hijo o contenedor. Con esta idea, los mensajes no manejados por el objeto hijo son enviados al padre, y potencialmente el padre del padre, hasta que se alcance el objeto indicado.

Este patrón es ùtil para el desarrollo de una interfaz de usuario.

Para implementar la Cadena de Responsabilidades, necesitas:

Handler - La interfaz que define el método usado para pasar un mensaje al siguiente handler. El mensaje es normalmente una llamada a un método, aunque si se necesitan más datos encapsulados, un objeto puede ser pasado también.

HandlerImpl - Una clase que implementa la interfaz Handler. Mantiene una referencia al siguiente Handler. Esta referencia es asignada en el constructor de la clase o a través de un método setter. La implementación del método que manejo de mensajes puede llamar a un método para manejarlo, reenviar el mensaje al siguiente handler, o ambos.

Ventajas y desventajas

La Cadena de Responsabilidades ofrece una gran flexibilidad en el procesamiento de eventos para una aplicación, ya que domina el manejo de eventos complejo dividiendo las responsabilidades a través de un número de elementos simples. Permite a un grupo de clases comportarse como un todo, ya que los eventos producidos por una clase pueden ser enviados a otras clases para que los atrapen dentro del grupo.
Por otro lado, la flexibilidad que este patrón provee tiene un precio: es difícil de desarrollar, testear y debuguear. A medida que se avanza la cadena se hace mas compleja, hay que ser cuidadosos viendo si los eventos están siendo correctamente reenviados.

Un error en el reenvío puede resultar en mensajes perdidos (mensajes que no pueden ser manejados por un handler y por lo tanto nunca tienen respuesta). También puede producirse un problema de alto volumen de mensajes y múltiples estados dentro de la cadena(chatter). Si muchos mensajes son producidos durante un periodo corto de tiempo y son pasados varias veces hasta que son atrapados, entonces el sistema puede volverse lento.

Variantes

Hay varias formas de adaptar la Cadena de Responsabilidades para alcanzar distintos requerimientos de las aplicaciones. Los dos criterios principales se basan en la estrategia de manejo y la estrategia de reenvío.

La estrategia de manejo se centra en cómo esta implementado el Handler. Algunas variantes pueden ser:

Dafault Handler - Algunas implementaciones tiene un handler de base, que es el handler por defecto para toda la cadena. Es usado normalmente solo cuando no hay una clase manejadora especificada. Un handler por defecto es muy útil para evitar el problema de mensajes perdidos mencionado anteriormente.

Manejar y Extender - En esta variante, el manejo de eventos tiene un comportamiento base a medida que el evento se propagan. Útil para logging.

Manejadores dinámicos - Algunas implementaciones permiten que la estructura de reenvío de mensajes cambien en tiempo de ejecución, usando un método setter para cada clase de la cadena. Esta flexibilidad deriva en una mayor complejidad.

Para la estrategia de reenvío se definen estas variantes:

Handle por defecto – Manejar cualquier mensaje que no es específicamente reenviado.

Propagar por defecto - reenviar cualquier mensaje que no es explícitamente manejado.

Reenviar al manejador por defecto - Más complejo que el patrón base, aquí se usa un manejador de eventos por defecto. Cualquier mensaje no manejado explícitamente en el componente, o no reenviado a otro manejador, va a ser enviado al manejador por defecto.

Ignorar por defecto - Cualquier mensaje que no es explícitamente manejado o reenviado es descartado. Si las clases en la cadena producen eventos que no son usados en la aplicación, esta puede ser una forma aceptable de reducir la sobrecarga. Sin embargo, se debe ser cuidadoso con este método para evitar descartar eventos que el sistema debería estar manejando.
 
 
Copyright © Slim code
Blogger Theme by BloggerThemes Design by Diovo.com