Ir a Tecsisa.com
6mar/09

Tutorial: Domain-Specific Languages (DSLs) en Apache Camel

Este tutorial es una introducción a los lenguajes de dominio específico o DSLs (Domain-Specific Languages). Nos centraremos en los DSLs internos mediante la creación de una sencilla ruta o pipeline de Apache Camel utilizando Java como lenguaje base.

¿Qué son los DSL?

Los DSLs son lenguajes de programación especialmente diseñados para desarrollar software restringido a un dominio determinado. A diferencia de los lenguajes llamados de propósito general como Java, C++ o C#, los DSLs cuentan con un universo limitado de aplicación. No obstante, gracias precisamente a esta especialización, presentan facilidades y ventajas a la hora de abordar los problemas de software para los que fueron diseñados y desarrollados.

Domain Specific LanguacePara definir e implementar lenguajes DSL es posible basarnos en un lenguaje de propósito general que sirva como contenedor, o bien partir de cero, requiriendo en este caso de específicos compiladores o intérpretes. Los DSLs del primer tipo citado suelen denominarse DSLs internos dejando la categoría de externos para los del segundo tipo.

En este tutorial veremos que es posible desarrollar un DSL íntegramente en Java poniendo como ejemplo el enfoque dado a las rutas o pipelines en Apache Camel. Además, haremos hincapié en las ventajas que presenta el uso de lenguajes DSLs, y de los DSLs internos en particular, citando también sus inconvenientes y subrayando en qué casos aplica de forma más natural un DSL externo.

Apache Camel

Apache Camel nació como un subproyecto de Apache ActiveMQ basado en algunas de las ideas de Apache ServiceMix. Apache Camel adquirió rápidamente popularidad y fue cubriendo necesidades que no se cubrían con Apache ActiveMQ, por lo que no tardó mucho tiempo en convertirse en un proyecto más de la ASF.

Apache Camel es un framework de código abierto que implementa la mayoría de los patrones de integración empresarial (EIP) descritos en el libro Enterprise Integration Patterns de Gregor Hohpe y Booby Woolf.

A lo largo del tutorial veremos cómo definir una ruta de Apache Camel utilizando un DSL, aunque esto también es posible si se emplea Scala DSL o XML.

Vamos a trabajar con el patrón de enrutamiento de mensajes, Content Based Router, con el que podemos leer mensajes de un endpoint de entrada y en función de su contenido redirigirlos a un endpoint de salida adecuado.

Caso de Uso

Implementaremos una solución muy sencilla de un pipeline o ruta de Apache Camel en la que leeremos mensajes de una cola de entrada y, en función de su contenido, generaremos distintos mensajes de salida.

Comenzamos el pipeline leyendo los mensajes de entrada de un componente timer que, cada dos segundos, genera un intercambio que viaja a través de la ruta.

A continuación se decide, en función del contenido del intercambio, qué acción llevar a cabo mediante los métodos choice, when y otherwise. Para tomar esta decisión utilizamos un Bean que genera un número aleatorio, lo suma al timestamp que se recibe del componente timer y devuelve un booleano indicando si el resultado es par o impar.

Si el resultado es par (el método isEven del beanisEvenTimeBean devuelve true) se añadirá una cabecera al mensaje que indica que el número es par. En caso contrario se añade una cabecera que indica que el número es impar. En ambos casos se tracea el contenido del mensaje mediante el componente log de Apache Camel.

Preparación del entorno

Para desplegar la solución utilizaremos la herramienta Apache Maven, por lo que es indispensable que la tengamos instalada en nuestro equipo.

En el código fuente de la solución (disponible en el enlace que se ofrece al final del tutorial) se puede observar la estructura que se debe implementar para adaptar nuestra solución a la jerarquía de directorios y ficheros de Apache Maven.

Vamos a utilizar el framework de Spring por lo que trabajaremos con los ficheros de configuración XML para definir un application context. Las rutas que definamos se inicializan y almacenan en el contexto de Apache Camel, por lo que lo primero que se debe hacer es inicializar el contexto a través del fichero app_context.xml. Para ello nuestro archivo main.java debe contener el siguiente código:

private static final String SPRING_APP_CONTEXT_FILE = "app-context.xml";

new ClassPathXmlApplicationContext(SPRING_APP_CONTEXT_FILE);

A continuación pasamos a definir la ruta que seguirán los mensajes que lleguen a nuestra aplicación. Esta ruta se encuentra definida en el fichero SimpleRouteBuilder.java.

Lo primero que debemos hacer para definir el pipeline es indicar de dónde se leerán los mensajes. En nuestro caso los leeremos del componente timer, que enviará cada dos segundos un intercambio:

from("timer://myTimer?fixedRate=true&period=2000")

En el siguiente paso se decidirá qué hacer con el mensaje en función de su contenido. Esto se consigue con la combinación de los métodos choice(), when() y otherwise(), que forman una estructura similar a la del switch. El método choice() sería análogo al switch, el when() al case y el otherwise() al default.

En nuestro caso, para decidir qué camino seguir, se utilizará el método isEven del Bean definido en el fichero IsEvenTimeBean.java que generará números aleatorios e indicará si son pares o impares:

.choice()

   .when().method("isEvenTimeBean", "isEven")

En el caso de que sean pares se añadirá la cabecera "numero par" y se traceará el intercambio utilizando el componente log de Apache Camel:

.setHeader("even").constant("numero par")

   .to("log:par?showAll=true&multiline=true")

Si son impares se añade "numero impar" y se tracea el intercambio:

.otherwise()

   .setHeader("even").constant("numero impar")

      .to("log:impar?showAll=true&multiline=true");

Para poder trabajar con el Bean debemos añadirlo al contexto de la aplicación, para lo cual añadiremos en el fichero app_context.xml la siguiente línea:

<bean class="com.tecsisa.art.dsl.camel.IsEvenTimeBean" id="isEvenTimeBean"/>

A continuación se muestra el aspecto que debe tener la ruta completa que hemos configurado:

from("timer://myTimer?fixedRate=true&period=2000")

   .choice()

      .when().method("isEvenTimeBean", "isEven")

         .setHeader("even").constant("numero par")

            .to("log:par?showAll=true&multiline=true")

      .otherwise()

         .setHeader("even").constant("numero impar")

            .to("log:impar?showAll=true&multiline=true");

Beneficios

Los DSLs en general permiten que un usuario familiarizado con el dominio del lenguaje pueda trabajar más cómodamente utilizando una semántica de dominio conocida. Otra ventaja que presentan los DSLs es que el código es auto-documentado, lo que facilita la lectura del mismo.

En cuanto a los beneficios del uso de los DSLs internos frente a los externos, cabe destacar la facilidad de programación. Esto es debido a que nos aprovechamos de las ventajas del lenguaje de programación que utilicemos como contenedor, Java en nuestro caso, lo que nos permite que el desarrollo de lógicas aplicadas a un dominio concreto sea más sencillo.

Además podemos seguir aprovechándonos de todas las facilidades que ofrecen los entornos de programación como Eclipse, NetBeans, IntelliJIdea, etc.

Inconvenientes

El principal inconveniente que presentan los DSLs internos frente a los externos es que, como utilizan un lenguaje como contenedor, cualquier cambio que se introduzca en el código implica que se debe volver a compilar y desplegar la solución, al contrario de lo que sucede con los lenguajes de scripting.

Finalmente, dado que son lenguajes específicos de un dominio, hay que evaluar la relación coste/beneficio de desarrollar un DSL frente a implementar la misma lógica en Java. En cada caso habrá que estudiar la conveniencia de abordar este enfoque o no.

Código fuente de la solución

El código fuente mostrado en este tutorial se encuentra disponible para su descarga pulsando aquí.

Gema Perdiguero Castellanos

Acerca de Gema Perdiguero Castellanos

Gema es consultora de Tecsisa
Comentarios (0) Trackbacks (0)

Sin comentarios por ahora.


Deja un comentario


Sin trackbacks por el momento.