lunes, 12 de septiembre de 2011

Abracadabra: componiendo criterios de hibernate en grails.

"Como decíamos ayer..."  Fray Luis de León, Miguel de Unamuno et alii
En la ultima aplicación que estoy escribiendo en el trabajo he tenido la suerte de usar grails como plataforma de desarrollo. Pese a todos los problemas que nos encontramos, casi todos originados en ser una tecnología relatívamente joven y que esta en continuo desarrollo, ha supuesto para nosotros un salto en el tiempo de ocho años. Hasta ahora estábamos usando la JDK 1.4, J2EE 1.4, struts 1 y persistencia manual en java y SQL, con lo que el cambio ha sido cualitativo.

En mis primerizos intentos de conseguir que el código sea reutilizable y evitar su duplicación no siempre he encontrado facilidades en la librería, o, lo mas seguro, no he sabido usarla de la forma correcta.

Uno de los puntos donde he encontrado dificultad ha sido a la hora de separar en elementos manejables las closures estáticas que contienen llamadas a métodos que forman un minilenguaje (o DSL) en si mismos. Estos métodos normalmente no existen en el contexto sintáctico donde están esas closures sino que pertenecen a algún otro objeto (normalmente un "builder"), aunque puede también usar las variables y métodos definidos en su ambito sintactico. En algún momento la librería "introduce" a la closure dentro del contexto del builder (por ejemplo con with) con lo que los métodos ya tienen sentido, la closure esta totalmente cerrada y puede ser ejecutada. Un ejemplo, usado en grails, es el uso de las Criteria de hibernate para definir consultas sobre entidades en la base de datos:


No tiene mala pinta pero una vez que te pones a usarlas te encuentras algunas limitaciones, una de ellas es que no puedes componer facilmente las closures para poder separar los diferentes criterios y hacerlos genéricos para no repetirlos una y otra vez. Si usas directamente el api de Criteria puedes componer los criterios creando instancias de los mismo y añadirlos a la instancia misma de la Criteria. Sin embargo en la solución de grails esto no es posible ya que aunque es mas breve y expresiva no expone los métodos para añadir dinámicamente criterios (o al menos no he encontrado la manera de hacerlo). Mi objetivo final era conseguir algo así:


O sea poder componer la closure final con closures mas pequeñas y manejables. También quería conseguir poder parametrizarlas tanto con datos externos como con los valores de la instancia en la que están definidas, consiguiendo un filtro que usa los campos de la instancia sobre la que es llamada como datos de ejemplo para realizar la consulta. En mi caso los métodos genéricos que encapsulan los criterios están en la superclase:



Solamente se añade el filtro de cada propiedad si esta informado y el carácter del filtro es lo mas abierto posible, usando "ilike" en los campos de cadena, "in" en los campos que definen relaciones uno a muchos con otras entidades, etc. También quiero subrayar que las entidades "maestras" se recuperan de forma recursiva, reutilizando el filtro definido en su clase correspondiente.

La pieza que falta para completar el puzzle es el operador ">>>" que se esta utilizando en el código para combinar las closures. Este operador no existe como tal en la versión actual de groovy. De hecho hace relativamente poco que en groovy (en la versión 1.8) se han añadido operaciones y métodos funcionales que actúan sobre las closures, tales como su aplicacion parcial y su composición. Supongo que la reciente recuperación del paradigma funcional (con lenguajes como Clojure, Scala o Haskell) ha favorecido que finalmente estén disponibles. En mi caso todavía estoy usando la versión 1.7 y tenia que añadir la operación de composición. Sin embargo al copiarla de la versión actual la cosa no marchaba. Lo vemos en este ejemplo que simula el comportamiento del builder de grails y la composición oficial de la versión 1.8:


Como se puede comprobar en la consola de groovy se produce un error ya que al componer ambas closures no se propaga el contexto de ejecución de la closure resultante a las closures que la componen, por lo que groovy busca los métodos "eq" e "in" en el contexto mas amplio que es el script mismo donde estamos escribiendo el código. Al no encontrarlo en ningún nivel nos devuelve el consabido error.

La closure se liga al contexto de un objeto usando la propiedad delegate de la misma. Para poder conseguir la ligazón tuve que implementar un método de composición que propagara esa propiedad de la closure resultante a las que están siendo compuestas:


Como se puede observar en el código "simplemente" se crea una nueva closure que llama a las dos que están siendo compuestas, a las que previamente les ha asignado su propio delegate para introducirlas en el contexto de computación. La implementación no esta muy cuidada y tal vez tenga errores, vamos, que no hay garantías.

La verdad es que no acabo de sentirme cómodo con la solución a la que he llegado, aunque en el camino he aprendido algo del funcionamiento interno de las closures, me parece que es demasiado enrevesada para lo que quería conseguir. En principio soy contrario al monkey patching más aun si se hace sobre clases básicas y al uso de la sobrecarga de operaciones. Seguramente se puede hacer de una manera más sencilla, aunque no la he encontrado. Como siempre vuestros comentarios tal vez puedan arrojar un poco mas de luz sobre el asunto.

Hasta mañana.