Feeds:
Entradas
Comentarios

Archive for the ‘DSL’ Category


Después del tocho sobre REST de la semana pasada, ahora toca cambiar de tercio. Seguramente algunos de vosotros habréis oído hablar de los DSL o Domain Specific Languages. En particular este es un tema en el que vengo trabajando desde hace años, y que me ha resultado bastante interesante y productivo.

¿Qué son los DSL? Simplemente podemos considerar como DSL cualquier lenguaje que esté especializado en modelar o resolver un conjunto específico de problemas. Este conjunto específico de problemas es el llamado dominio de aplicación o de negocio. La mayor parte de los lenguajes de programación no se pueden considerar DSL ya que no están diseñados para resolver un conjunto específico de problemas, sino para resolver cualquier tipo de problema. Son pues, lenguajes generalistas (o turing completos).

El usar lenguajes generalistas ha tenido bastante aceptación y éxito hasta el día de hoy, de hecho poseen una ventaja indudable: con dichos lenguajes podemos resolver cualquier tipo de problema. Sin embargo esta ventaja no es gratis, al contrario, hay que pagar un precio, que es que no todos los problemas son igualmente sencillos de resolver. De hecho, dependiendo del lenguaje generalista que usemos, un mismo problema es más sencillo o más difícil de resolver. Es gracioso que se hayan producido muchas controversias por cual es el mejor lenguaje, cuando en realidad unos lenguajes son buenos para una cosa y malos para otra, y que los principales defensores de un lenguaje sólo tienen en cuenta las tareas en las que su lenguaje favorito es bueno. Normalmente suelen ser las mismas tareas que ellos se encuentran en su día a día laboral. Por ejemplo, trabajar con listas o hacer algoritmos recursivos es muy simple en LISP, sin embargo en BASIC o COBOL no es tan sencillo. Supongo que en cambio, con COBOL es sencillo definir algoritmos de negocio. Otro ejemplo, generar una página web dinámica es relativamente sencillo en PHP pero más complejo en JAVA. ¡ Eh ! Espera, en JAVA hacer una página web está tirado, pones una JSP y entonces… No hombre, el lenguaje JSP no es lenguaje JAVA, es un DSL…

Si cada lenguaje puede resolver cualquier problema, pero no todos con la misma sencillez, es lógico pensar que para cada problema que tengamos, podemos buscar el lenguaje que mejor se adapte a él. Al fin y al cabo, hay que usar la herramienta adecuada para cada caso. En su momento algunas mentes preeminentes se dieron cuenta de que el problema consistía precisamente en que los lenguajes generalistas eran demasiado ambiciosos al tratar de poder solventar cualquier problema. De hecho sería más adecuado poder contar con lenguajes especializados en solucionar los problemas que tuviéramos a mano. Es la misma diferencia entre tener un cuchillo multiuso para cualquier cosa, o un set de cuchillo de postre, de carne, de pescado y un bisturí y elegir los cuchillos que mejor nos vengan. Si sé que ese día voy a practicar una operación a corazón abierto, y después comeré pescado, mejor me llevo el bisturí y la paleta de pescado (para ser cirujano hay que tener el estomago muy fuerte).

De esta forma nació el enfoque de los DSL, en vez de usar un lenguaje generalista, puedo usar diversos lenguajes de propósito específico, para resolver los diferentes problemas que surgen al implementar una aplicación. Por lo tanto sería muy eficiente contar con varios DSL, uno para cada tipo de problema con el que nos solemos encontrar, y para los problemas no tan típicos (esperamos que el 20% o menos) solucionarlos con lenguajes generalistas. Algunos pensareis que esto de anticipar los problemas que voy a tener es un poco irreal, pues ni mucho menos. Desde el punto de vista puramente técnico, existen problemas que voy a tener que resolver casi siempre:

  • Definición de interfaces de usuario.
  • Seguridad. Aquí podemos distinguir dos subproblemas separados: autenticación y autorización.
  • Gestión transaccional.
  • Persistencia.
  • Queries.
  • Validación.
  • Definición de tests automatizados (no me puedo resistir a meter TDD con calzador, je, je)
  • Análisis léxico y sintáctico de lenguajes (parser) para construir compiladores

Obviamente estos son sólo algunos de los problemas técnicos que nos solemos encontrar día a día, hay muchos más claro ¿No sería interesante contar con un DSL específico para resolver estos problemas? De este modo podríamos especificar en cada uno de los DSLs los requisitos de nuestro sistema de forma específica para cada tipo de problema.

Aunque parezca mentira, ya existen algunos DSL que conviven con nosotros, pero tal vez no hemos pensado en ellos como tales. Algunos ejemplos:

  • SQL para definir queries.
  • XML para transportar datos, y a veces nos viene bien para persistir.
  • HTML para definir interfaces de usuario. Curiosamente este DSL se ha ido “extendiendo” para ir convirtiéndolo cada vez más en un lenguaje generalista. No es de extrañar que sea difícil definir aplicaciones usando enteramente HTML.
  • CSS para definir interfaces de usuario a nivel de presentación pura.
  • JSP. Para definir (otra vez) interfaces de usuario web.
  • WSDL, para especificar interfaces de datos de servicios web.
  • XML de configuración de spring, para definir grafos de objetos.
  • XML de hibernate para hacer mapeo entidad/relación.
  • jMock para definir Mock Objects.
  • ANTLR para definir gramáticas LL(*)
  • Expresiones regulares (no se puede vivir sin ellas)

Como vemos, ya estamos conviviendo con DSLs, aunque no nos hayamos dado cuenta. De hecho existe un ciclo de vida en la evolución de una solución a un problema dentro del software. Primero resolvemos el problema como se nos ocurra. Después, conforme ganamos experiencia, al resolver el problema varias veces en contextos distintos, encontramos buenas prácticas o “recetas”, que nos permiten resolver el problema mediante copy/paste. En una etapa posterior de maduración, conseguimos abstraer esas recetas, en una librería que sigue un determinado diseño, y obtenemos un patrón de diseño al abstraer el diseño de la implementación en si. Una vez que tenemos varios patrones de diseño que resuelven distintos problemas de la misma categoría, podemos implementarlos juntos, y obtenemos un framework. Finalmente, y este es el paso menos conocido, el framework se hace cada vez más configurable y automatizado, con lo que éste termina evolucionando en un DSL. Como ejemplo tenemos el caso de la inyección de dependencias: todo surgió por la necesidad de instanciar objetos, se vio que era interesante aprovechar el polimorfismo y usar interfaces, después se decidió que no era bueno usar “new” porque entonces no podíamos cambiar la implementación de la interfaz alegremente, por lo que surgió el patrón “factory method”, que después evolucionó en el patrón “factory”, al evolucionarlo a una “factory” configurable nació la inyección de dependencias y de ahí Spring y su XML, que es un DSL para configurar “factories”.

No quiero que os quedeis con la idea de que los DSLs son cosas que resuelven problemas “técnicos”. Hasta el momento he mostrado lo que son los DSLs “técnicos”, pero es que también existen DSLs de negocio. Existen muchos dominios de negocio, podemos tener aplicaciones de banca, de inversión en bolsa, tiendas electrónicas, seguros, etc. Por supuesto cada área se puede subdividir en dominios más pequeños. Por lo tanto parece una buena idea el poder definir DSLs de negocio, que nos permitan especificar, de forma sencilla y eficaz, los distintos conceptos, reglas y validaciones, de un dominio de negocio concreto.

Así pues, desde el punto de vista del tipo de problemas que resuelven, tenemos dos tipos de DSL, DSLs de negocio y DSLs técnicos. Sin embargo existe otra forma diferente de categorizar los DSLs, según el enfoque a la hora de implementarlos. De esta manera tenemos dos tipos:

  • DSLs externos. Un DSL externo es un lenguaje en si mismo, independiente de cualquier otro lenguaje. Posee su propia sintaxis, compilador y/o interprete, e incluso puede llegar a tener su propio IDE. Ejemplos de DSLs externos son SQL o ANTLR. Los DSLs externos son el típico caso en el que uno piensa cuando se imagina un DSL, un lenguaje nuevo y aparte de los que ya existen.
  • DSLs internos. En este enfoque no construimos un lenguaje desde cero, sino que aprovechamos un lenguaje ya existente para construir nuestro DSL. Usando un poco de imaginación y exprimiendo los trucos del lenguaje anfitrión y su sintaxis, podemos construir una librería o framework que implemente el DSL de forma nativa al lenguaje anfitrión. Algunas técnicas usadas son los patrones “method chaining” y “builder”, el uso de métodos factoría estáticos, import estáticos, tipos y métodos paramétricos (genéricos) y por supuesto mucha reflexión, metaprogramación y los siempre agradecidos proxies dinámicos. Un excelente ejemplo de DSL interno es el propio jMock. Los DSL internos también son llamados “fluent interface” o “fluent API”. Yo, pobre de mi, no sabía que existía esta denominación, por lo que se me ocurrió llamar “API Sexy” a un DSL que había construido, con el consiguiente cachondeo de mis compañeros.

Si los DSLs son tan maravillosos, ¿por qué no estamos todos usándolos de forma más intensiva? El problema principal es el coste de desarrollar un buen DSL. Como norma general, el enfoque tradicional hacia los DSL ha sido el de DSL externos. Construir un DSL externo es bastante costoso, en realidad es construir un lenguaje de programación nuevo, con su compilador o interprete. Necesitamos saber análisis léxico, sintáctico, transformación de ASTs, generación de código, etc. Vamos todo lo que nos enseñaron en la asignatura de compiladores. Normalmente para esto contamos con herramientas como ANTLR, javaCC, Lex, Yacc, etc. Evidentemente todo esto es complejo y lamentablemente no está al alcance del desarrollador medio de aplicaciones web de empresa (sic). Otro punto problemático es el IDE, si yo me construyo un DSL externo, el IDE no me va a dar soporte, ni a nivel de errores de compilación, ni a nivel de refactorización, ni tan siquiera ayuda contextual (no sabemos vivir sin el CTRL+SPACE). Si quiero todo esto me tengo que construir un IDE nuevo o extender uno existente, tarea de nuevo costosa. Obviamente la ventaja de un DSL externo frente a uno interno es que el DSL tendrá la sintaxis y gramática que queramos, mientras que el DSL interno estará restringido por el lenguaje anfitrión.

Sin embargo el enfoque DSL ha tenido un resurgimiento al introducirse el concepto de DSL interno. Un DSL interno es mucho más sencillo de construir, no difiere mucho de construir un framework, tal vez se necesita una dosis extra de uso de reflexión y metaprogramación, pero al fin y al cabo no tenemos que andar con gramáticas y demás. Por otro lado, al no ser en realidad más que un API del lenguaje anfitrión, el IDE nos va a proporcionar ayuda. Ciertamente no todos los lenguajes son lo suficientemente flexibles como para permitir construir un DSL interno. Es casi imposible hacerlo en COBOL o en C (que yo sepa). En JAVA la dificultad es media. Pero en algunos lenguajes como Groovy, ¡resulta sencillo! En general cuanto más flexible sea el sistema de tipos del lenguaje y su sintaxis, y cuanta más capacidad de metaprogramación tenga, más sencillo construir un DSL interno. Lenguajes como Groovy o Ruby son ideales para esto, mientras que lenguajes con tipado fuerte suelen ser más problemáticos.

Sin entrar en como construir un DSL interno, veamos algunos ejemplos en Groovy:

  • Basándonos en el concepto de builders de groovy podemos definir un XML dinámico:
    import groovy.xml.MarkupBuilder
    StringWriter writer = new StringWriter()
    def build = new MarkupBuilder(writer)
    build.books
    {
      book(id:'Juan Palomo', price:23.44.euro)
      {
        author(ref:'author23')
        author(ref:'author3')
        synopsis 'En un bar lejano...'
      }
      book(id:'Juan Palomo 2', price:34.euro)
      {
        author(ref:'author3')
        synopsis 'De vuelta a casa...'
      }
      book(id:'La vida loca', price:3.44.euro)
      {
        author(ref:'author12')
        synopsis 'En un bar cercano...'
      }
    }
    println writer.toString()
  • O quizás, usando un hipotético servicio REST:
    import groovy.xml.MarkupBuilder
    def dataSource=new myrest.RESTDatasource('https://www.bookstore.com/shop/books?bestSeller=true');
    StringWriter writer = new StringWriter()
    def build = new MarkupBuilder(writer)
    build.books
    {
      dataSource.each
      {
        book(id:it.title, price:it.price.euro)
        {
          authors.each { author(ref:it.id) }
          synopsis it.synopsis
        }
      }
    }
    println writer.toString()

No esta mal, ¿eh? ¿Quién quiere hacer lo mismo con JSP o JAXB?

Desde otro punto de vista, ya sea usando DSL externos o internos, una de las cosas más interesantes es hacer DSL orientados a negocio. Esto puede ser un paso adelante a la hora de solucionar un antiguo problema del software: la mala comunicación entre usuario y desarrollador. Tradicionalmente se ha usado al analista de negocio para cubrir ese hueco, pero en la práctica los problemas de comunicación entre el analista de negocio y el desarrollador no son despreciables. Si pudiéramos escribir los requisitos de negocio, en un lenguaje de programación, pero que a la vez fuera legible por un cliente o en su defecto, el analista de negocio, se ahorrarían muchos malentendidos. Un opción muy de moda es el modelado visual, pero claro, será visual, pero sigue haciendo falta la habilidad de modelar formalmente, habilidad que no tienen muchos clientes y expertos de negocio. Ellos saben de su negocio, pero no necesariamente saben plasmarlo en un modelo visual. Otro problema es que si bien algunos dominios de negocio se prestan muy bien al modelado visual, otros no. ¿Cómo modelamos visualmente un portfolio de acciones y derivados? Finalmente, para modelar visualmente, suele ser necesaria una herramienta, normalmente cara. En este sentido los DSL son superiores a las herramientas de modelado visual. Por un lado, el DSL está desarrollado específicamente para el dominio de negocio, con lo que son fáciles de usar para definir el negocio. Por otro lado, no se necesita una herramienta visual. Y finalmente, no se pretende que el experto de negocio escriba el DSL, sólo que sea capaz de leerlo, entenderlo y validarlo, que es lo realmente importante, escribir código ya lo hacemos nosotros.

Veamos algunos ejemplos de DSL de negocio (seguimos con Groovy, ¡cómo me gusta!):

  • Manejemos algo de dinero:
    def account=people
      .find(id: '22')
      .defaultAccout
    if(account.balance > 4000.pound + 30.dollar)
      account.transferTo(
        accountId:'24',
        amount: 20.euro + 30.dollar - 4.pound)
  • Ya que tenemos dinero, podemos invertir en bolsa:
    people.find(id:'0345')
      .buyOffer
      {
        buyPosition(desired: 35.ibm, maxPrice:34.euro)
        buyPosition(desired: 2.google, maxPrice:43.euro)
        buyPosition(desired: 10.microsoft, maxPrice:15.euro)
      }

Por supuesto, no soy experto en este tipo de negocios, así que alguna barbaridad habré puesto. Pero coincidireis conmigo que trabajando en equipo con un experto de negocio puede salir algo realmente potente, que nos permita definir test de aceptación, reglas de negocio y validaciones, de forma sencilla y legible.

El hecho de usar DSL para agilizar la comunicación con el cliente produce una sinergia con el uso de TDD, BDD y MDD (Model Driven Development). En TDD y BDD, podemos definir los tests de aceptación directamente en nuestro DSL de negocio, con lo que las pruebas serán más fáciles de escribir y validar. Desde el punto de vista de MDD, el DSL de negocio no es más que una API montada sobre el modelo OO de negocio, que nos permite construir modelos de negocio usando un lenguaje cercano al negocio. O sea, si usamos MDD, deberíamos aprovechar el modelo de negocio como base para nuestro DSL, de hecho podríamos considerar el DSL como un API builder para construir instancias de modelos de negocio y operar con ellos.

Un último detalle, algunos pensareis que pongo demasiado énfasis en la legibilidad del código, pues bien, el caso es que el código se lee mucho más frecuentemente que se escribe. La lectura y comprensión del código es una acción más frecuente que la escritura de este, y por lo tanto prefiero favorecer la legibilidad con respecto a la escritura. Si lo pensáis bien, este es la razón de que cosas como la generación de código y los wizards, al final no suelan ser muy útiles en lo que realidad importa, el mantenimiento correctivo y evolutivo de la aplicación. En esto los DSLs dan una ventaja grande, ya que proporcionan legibilidad y a la vez suelen simplificar la escritura.

Guau, menos de tres mil palabras, ¡ todo un récord ! Buenas noches.

Read Full Post »

Seguir

Recibe cada nueva publicación en tu buzón de correo electrónico.

Únete a otros 43 seguidores