Lenguaje específico de dominio (interno/embebido)

Un lenguaje específico de dominio, o DSL por su sigla en inglés, es un lenguaje personalizado creado para resolver problemas en un dominio específico. En un software de contabilidad, por ejemplo, el dominio del problema incluye conceptos como estados de cuenta y operaciones como la conciliación de cuentas.

Un DSL interno o embebido se implementa empleando un lenguaje de programación de alto nivel como Java, Ruby, Python o Scala y sus características están limitadas por las capacidades y reglas sintácticas del lenguaje elegido para su implementación. Por ejemplo, si el lenguaje no soporta el paso de bloques de código como argumento en la llamada a un método, difícilmente podríamos aplicar el patrón Closure a la hora de implementar un DSL en dicho lenguaje.

La principal motivación para el desarrollo de un DSL interno es mejorar la claridad con la que el código expresa su propósito de manera tal que incluso usuarios no-programadores puedan comprenderlo y validar si satisfacen los requerimientos de negocio.

El ideal es lograr que el código sea una descripción de como se realiza una tarea de negocio lo más cercana al lenguaje natural:

    Orden orden = paraCliente("María Marquez")
					.comprar(80)
					.accion("IBM")
						.enMercado("NYSE")
					.por(USD(125.00))
				.fin();

Claramente, el código anterior es muy parecido a la frase «para el cliente María Marquez, comprar 80 acciones de IBM en el mercado NYSE por 150 USD». El código esconde al lector aspectos del lenguaje de programación (constructores, getters/setters o enumeraciones) y permite combinar con fluidez conceptos y operaciones del dominio del problema.

Patrones para la implementación de DSL internos en Java

Cuando se implementa un DSL, es fundamental controlar el contexto en el que se invoca un método o se evalua una expresión para no permitir código que viole las reglas de negocio. A continuación se demuestran los patrones que suelen emplearse para implementar DSLs internos en lenguajes con tipado estático como Java.

La proliferación de builders sólo es necesaria cuando el DSL requiere imponer cierto orden en la ejecución de tareas o configuración del modelo de dominio. En el ejemplo del vídeo, tiene bastante sentido que el precio se establezca después de haber definido el tipo de transacción mediante los métodos buy o sell.

Hay DSLs en los que no se requiere imponer un orden, por ejemplo, este es un DSL para definir la copia de datos de una tabla desde una base de datos a otra:

BulkCopyProcess.forTable(table)
		.startingAt(startDate)
		.endingAt(endDate)
		.onEnvironment( BulkCopyEnvironment.PROD )
		.run();

Una vez definida la tabla que se quiere copiar y siempre que se incluyan todos los argumentos las sentencias son igualmente entendibles independientemente del orden en el que se incluyan los argumentos:

BulkCopyProcess.forTable(table)
		.onEnvironment( BulkCopyEnvironment.PROD )
		.startingAt(startDate)
		.endingAt(endDate)
		.run();

Para implementar este DSL se requiere que el método forTable retorne un builder con los métodos onEnvironment, startingAt, endingAt y run.

Referencias

[1] – Raoul-Gabriel Urma, Mario Fusco and Alan Mycroft Modern Java in Action – Lambdas, streams, functional and reactive programming. Manning, 2019
[2] – Martin Fowler and Rebecca Parsons Domain-Specific Languages. Addison-Wesley Professional, 2010.
[3] – Martin Fowler Domain-Specific Languages Guide. 2019.

Comments are closed.