Creando DSL's con Groovy: los Builders
jueves 27/12/2007
DSL significa Lenguaje de Dominio Específico (Domain Specific Languages por sus siglas en Inglés), que en contraste con lenguages de propésito general, como lo son C, Java e incluso Groovy, proveen una mejor integración entre el problema a resolver y la solución a aplicar en términos del modelo en particular y no en términos del lenguage computacional seleccionado para implementar dicha solución. Groovy ofrece varias opciones para crear tu propio DSL, siendo una de ellas, y el tema de este artículo, los Builders ("Constructores").
Los builders permiten construir estructuras jerárquicas de manera natural, eliminando la complejidad de que el desarrollador administre la creación de la estructura al mismo tiempo que el código de negocio. Veamos un ejemplo sencillo utilizando uno de los builders que Groovy 1.5.0 provee: ObjectGraphBuilder. Este builder permite crear un grafo de objetos que cumplen con la convención de JavaBeans, es decir, por cada propiedad del objeto existen dos métodos (get/set) para obtener/asignar el valor; aquellas propiedades que representen un conjunto de valores seran de tipo java.awt.List
Listado 1.1
class Empresa {
List empleados
String nombre
Direccion direccion
}
class Empleado {
String nombre
Direccion direccion
}
class Direccion {
String calle
int numero
}
def ogb = new ObjectGraphBuilder()
def acme = ogb.empresa( nombre: 'ACME' ) {
direccion( calle: 'Avenida Circuito Cerrado', numero: 1 )
empleado( nombre: 'Pepe' ) {
direccion( calle: 'Calle Soledad', numero: 1001 )
}
}
assert acme.empleados.size() == 1
assert acme.empleados.name = ['Pepe'] // expresion GPath
Adios a llamadas explícitas a setNombre, cero problemas en adicionar un Empleado a la lista de empleados de Empresa. ObjectGraphBuilder provee otras habilidades pero el punto es, ¿cómo le hizo para saber que un metodo 'empresa' debia construir una instancia de la clase Empresa? de igual manera ¿como le hizo para resolver las llamadas a los otros métodos? El truco está en cómo funcionan los builders: toda llamada de método es interceptada, aquellos métodos que si existan en el builder, es decir estan declarados en la clase, son ejecutados directamente; aquellos que no (como el caso de empresa en el ejemplo anterior) son tratados de manera especial. El builder tratará de localizar alguna rutina interna que pueda resolver dicho metodo, en el caso de ObjectGraphBuilder este posee un grupo de fábricas de objetos que siguen las reglas de la convención JavaBeans para crear instancias y alambrar propiedades.
De esta misma manera funciona SwingBuilder, uno de los builders más conocidos de Groovy, cuya estrategia en términos llanos es traducir el nombre del nodo, como por ejemplo 'frame', agregando una J al principio, en este caso JFrame. En realidad es mas complejo que eso pero es la idea que se percibe al ver el código producido, como el siguiente
Listado 1.2
import groovy.swing.SwingBuilder
def swing = SwingBuilder.build {
frame( id: 'frame', title: 'Groovy + Swing', size: [200,100] ) {
label( 'Hola Groovy' )
}
}
swing.doLater{ frame.visible = true }
Groovy ofrece 3 opciones para que puedas crear tus propios builders:
- extendiendo GroovyObjectSupport, es tu responsabilidad alambrar la lógica al sobreescribir invokeMethod/methodMissing principalmente
- extendiendo BuilderSupport, la manera estandar de muchos builders que existen en la distribución de Groovy
- extendiendo FactoryBuilderSupport, la manera mas moderna, aumenta los mecanismos provistos por BuilderSupport
Ejemplo de la primera categoría es BeanBuilder (disponible en Grails), el cual permite crear objetos de tipo ApplicationContext (de Spring). La razón por la que sigue esta opción es por que provee tratamiento especial al acceso de propiedades. Ejemplo de la segunda categoría es MarkupBuilder, tambien usado en Grails comunmente, el cual permite escribir HTML/XML de manera sencilla. Ejemplo de la tercera categoría son SwingBuilder y ObjectGraphBuilder, los cuales permiten el registro de fábricas adicionales y extensiones en determinados puntos de la creación de un nodo a través de adición de closures (descrito en la página de FactoryBuilderSupport).
Vemos que sucedería si seguimos la primera opción para implementar un builder similar a ObjectGraphbuilder. Asumiendo que el dominio se compone solamente de Empresa, Empleado y Direccion la implementación de dicho builder es relativamente sencilla, es mas, podría tener los siguientes métodos
Listado 1.3
class ModelBuilder {
Empresa empresa( Map parametros ){ /* implementacion */ }
Empleado empleado( Map parametros ){ /* implementacion */ }
Direccion direccion( Map parametros ){ /* implementacion */ }
// y como se maneja el stack para insertar propiedades ??
}
De alguna manera esos tres métodos deben alambrar las propiedades y contemplar que direccion() puede invocarse dentro de empresa() y empleado(). ¿Que pasaría si se agrega una cuarta clase de dominio? ¿o si se anexan mas propiedades? el código empieza a complicarse. Para evitarlo, podemos tratar de simplificar el código un poco (y de paso eliminar repeticiones) al utilizar invokeMethod/methodMissing
Listado 1.4
class ModelBuilder {
public Object methodMissing( String name, Object args ) {
switch( name ){
case "empresa": // crear Empresa y manejar stack de propiedades
case "empleado": // crear Empleado y manejar stack de propiedades
case "direccion": // crear Direccion y manejar stack de propiedades
}
}
}
Aún asi hay demasiado código extra y falta definir como se asignan propiedades. Veamos entonces cómo sería con la segunda alternativa
Listado 1.5
class ModelBuilder extends BuilderSupport {
/*
* createNode es un metodo plantilla provisto por BuilderSupport
* la super clase se encarga de alambrar el stack de llamadas
*/
public Object createNode( String name, Map properties ){
def node = null
switch( name ){
case "empresa": node = new Empresa(properties); break;
case "empleado": node = new Empleado(properties); break;
case "direccion": node = new Direccion(properties); break;
}
}
/*
* setParent es otro metodo plantilla util para alambrar una propiedad
* (nodo hijo) en un nodo padre.
*/
public void setParent( Object padre, Object hijo ){
if( padre.class == Empresa && hijo.class == Direccion ) padre.direccion = hijo
// codigo similar para otros casos
}
}
Esta versión de ModelBuilder es más robusta y en teoría proporciona mejor soporte a cambios en el modelo, sin embargo la forma como se asignan las propiedades es rígida, seguro hay alguna manera de hacerlo mas génerico, también hay que tomar en cuenta aquellas propiedades que forman parte de una colección, como es el caso de Empresa.empleados. Por supuesto que hay que cosiderar a su vez también el uso de clases en distintos paquetes, lo cual hace mas difícil el mantenimiento del builder. Para resolver estos problemas existe la tercera opción: extender FactoryBuilderSupport. FactoryBuilderSupport permite la creación de nodos a partir de fábricas, las cuales abstraen todo lo que un nodo necesita para construirse, asignar propiedades y participar en el ciclo padre/hijo. Extendiendo FactoryBuilderSupport te libera de el teje-maneje de la jerarquía en un 95%, las fábricas deben tratar la asignación de padre/hijo al menos de manera genérica. Para el caso de ObjectGraphBuilder, si sigues las convenciones, solo basta con instanciar el builder y usarlo, como en el primer ejemplo, claro esta que éste te permite mas opciones, descritas en la pagina de ObjectGraphBuilder.
Cualquiera que sea la opción que decidas tomar encontraras suficientes ejemplos de cada categoría tanto en Groovy como en Grails.

Andrés Almiray es un Programador Java certificado, Desarrollador de Aplicaciones Web certificado, con más de 7 años de experiencia en diseño y desarrollo de aplicaciones. El ha estado involucrado en muchos desarrollos de aplicaciones web y desktop seleccionando el conjunto de herramientas y tecnologías mas apropiadas. También ha sido profesor de cursos de ciencias de la computación en el instituto educativo más prestigioso de México. Sus interese actuales incluyen arquitectura de software, Developer Testing, Groovy, Spring, AOP y Swing.














Venkman - domingo 30/12/2007
La traducción de DSL es más bien Lenguaje de Dominio Específico, no lenguaje específico de dominio.