viernes, 1 de noviembre de 2013

Transacciones en Apache Tapestry

Apache Tapestry
Tapestry no proporciona de por sí un soporte completo a las aplicaciones que necesitan realizar operaciones en una base de datos relacional de forma transaccional. La dependencia tapestry-hibernate provee la anotación @CommitAfter para Hibernate y la dependencia tapestry-jpa otra del mismo nombre para JPA pero esta anotación en ambos casos proporciona una funcionalidad muy básica y probablemente no no sirva en casos de uso complejos. Esto ha sido objeto de discusión varias veces en la lista de distribución de los usuarios [1] [2] y el JIRA de Tapestry [3].

Con la anotación CommitAfter si se produce una excepción no controlada («unchecked») se hará un rollback de la transacción y, esto es importante, aún produciendose una excepción controlada («checked») se hará el commit de la transacción y es responsabilidad del programador tratar la excepción adecuadamente. Se puede usar en los métodos de los servicios y en los métodos manejadores de eventos de los componentes.

Sabiendo como funciona la anotación se nos plantean preguntas:
  1. ¿Cuál es el comportamiento cuando un método del servicio anotado llame a otro también anotado del mismo servicio?
  2. ¿Que pasa si cada método está en un servicio diferente?
Para el primer caso (métodos del mismo servicio) se hará una sola transacción ya que las anotaciones y los advices se aplican en el proxy del servicio no en la implementación. En el segundo caso (métodos en diferentes servicios) se iniciará una transacción pero haciendo un commit en la salida de cada método.

Si tenemos una aplicación compleja probablemente se nos planteará el caso de tener varios servicios que se llaman entre si y que ambos necesiten compartir la transacción, en esta situación la anotación CommitAfter probablemente no nos sirva por hacer un commit en la salida de cada método.

Tapestry no pretende proporcionar una solución propia que cubra todas las necesidades transaccionales que puedan tener todas las aplicaciones sino que con la anotación CommitAfter pretende soportar los casos simples, para casos más complejos ya existen otras opciones que están ampliamente probadas. Si necesitamos un mejor soporte para las transacciones que el que ofrece Tapestry debemos optar por Spring o por los EJB. Sin embargo, la solución de Spring nos obliga a definir los servicios transaccionales como servicios de Spring y los EJBs nos obligan a desplegar la aplicación en un servidor de aplicaciones que soporte un contenedor de EJB como JBoss/Wildfy, Gernimo, TomEE, ... Si nuestro caso no es tan complejo como para necesitar mucho de lo que ofrece Spring o no queremos o podemos usar un servidor que soporte EJB podemos aplicar el ejemplo ya comentado en Tapestry Magic #5: Advising Services.

En esta entrada pondré un ejemplo completo usando la solución de Tapestry Magic #5 pero con la adición de una anotación que permite definir más propiedades de las transacciones y la diferencia respecto a la anotación CommitAfter de que independientemente de si se produce una excepción checked o unchecked se hace un rollback de la transacción. La anotación Transactional permite definir si la transacción es de solo lectura, definir un timeout para completar la transacción o el nivel de aislamiento de la transacción además de la estrategia de propagación. Aunque no sea una solución tan buena como la de usar Spring o EJBs, puede ser suficiente para muchos más casos que la anotación CommitAfter.

La solución consiste en implementar una nueva anotación para los métodos transaccionales que he llamado Transactional, unos advices con las diferentes estrategias de transaccionalidad (REQUIRED, SUPPORTS, NEVER, NESTED, MANDATORY), un advisor que aplicará una estrategia transaccional en función de la anotación Transactional de los métodos y un poco de configuración para el contenedor IoC que define los servicios y aplica la decoración a los métodos anotados.

Hay que tener en cuenta que esta solución es una prueba de concepto que he probado en este ejemplo y puede presentar problemas que aún desconozco en una aplicación real. Una vez dicho esto veámos el código.

Primero la anotación, el enumerado de las estrategias de propagación de transacciones, el DTO (Data Transfer Object) con las propiedades de la anotación y la interfaz del servicio transaccional.

Ahora el advisor que usará el servicio transaccional y en función de la estrategia de propagación aplicará el advice adecuado.

El funcionamiento de las estrategias transaccionales son:
  • REQUIRED: si no hay una transaccion activa inicia una y hace el commit al finalizar. Si existe una al entrar en el método simplemente ejecuta la lógica usando la transacción actual
  • MANDATORY: requiere que haya una transacción iniciada, en caso contrario produce una excepción.
  • NESTED: inicia una nueva transacción siempre aún existiendo ya una, con lo que puede haber varias transacciones a la vez de forma anidada.
  • NEVER: es el caso contrario de MANDATORY, si existe una transacción produce una excepción.
  • SUPPORTS: puede ejecutarse tanto dentro como fuera de una transacción.
Probablemente con la anotación REQUIRED tengamos suficiente para la mayoría de los casos, para la estrategia NESTED necesitaremos soporte del motor de la base de datos que no todos tienen, el resto son otras posibilidades comunes en el ámbito de las transacciones: MANDATORY, NEVER, SUPPORTS.

Y ahora las implementaciones de las estrategias de propagación que iniciarán, harán el rollbak y commit de forma adecuada a la estrategia usando el servicio transaccional.

A continuación la implementación del servicio transaccional para el caso de Hibernate.

Finalmente el gestor de sesiones de Hibernate y la configuración necesaria en el módulo de la aplicación.

Si te ha parecido interesante esta entrada puedes descargar el libro PlugIn Tapestry en el que explico más en detalle como desarrollar aplicaciones web en Tapestry y en el que descubrirás como resolver problemas comunes en las aplicaciones web de una forma tan buena como esta.

Si quieres probarlo en tu equipo lo puedes hacer de forma muy sencilla con los siguientes comandos y sin instalar nada. Si no dispones de git para clonar mi repositorio de GitHub puedes obtener el código fuente del repositorio en un archivo zip.

Referencia:
Integración y transacciones con Spring en Apache Tapestry