Una clave en la transformación digital de nuestros clientes, además de acompañarlos durante todo el proceso, es comenzar poco a poco modernizando sus herramientas y procesos, consiguiendo cambios disruptivos con un esfuerzo controlado que no impliquen hacerlo todo desde cero. Un ejemplo de esto sería la reutilización de recursos ya existentes y consolidados en el cliente a los que darles un nuevo valor añadido; nuevas formas de uso, funcionalidades, etc.

Este post se centra en la creación de un servicio de generación de informes a partir de las plantillas que ya tiene el cliente, concretamente JasperReports. Los objetivos son que cada sistema de información no tenga que implementar sus propios mecanismos para generar informes, aprovechar los conocimientos del propio cliente en la creación de plantillas y explotar las ventajas que nos ofrece la computación en la nube. Utilizaremos la solución serverless AWS Lambda, pero es extensible a la tecnología FaaS de cualquier otro proveedor cloud.

JasperReports, ese viejo conocido

Si preguntamos a cualquier desarrollador Java por algún sistema para elaborar informes a partir de plantillas, la respuesta está clara: JasperReports. Es verdad que hay otras librerías con similares características, pero JasperReports lleva entre nosotros desde 2001 y esto lo convierte en un veterano asentado en el mercado. De este modo, es muy probable que los sistemas de información de nuestro cliente, que puedan tener más de una década, utilicen JasperReports. A lo largo de los años habrán cambiado su imagen de marca, los tipos de informes e incluso pueden que hayan ido actualizando las versiones de la librería. Esto nos posiciona en una situación clave, en la que con un pequeño esfuerzo podemos implementar un nuevo sistema cloud de generación de informes aprovechando todas las plantillas que ya posee el cliente, permitiendo que pueda seguir modificándolas, creando nuevas y todo ello con una herramienta que conoce ampliamente.

A la hora de modernizar o transformar un sistema y prepararlo para el cloud, un paso fundamental es “partir” el monolito. Hay varias formas de hacerlo; entre ellas, por bloques funcionales. Y qué funcionalidad más clara y diferenciada del resto del sistema que la de generación de informes. Es una funcionalidad que se puede encapsular fácilmente y, aunque depende del tipo de sistema, por regla general no es algo que se utilice constantemente; pueden agrupar datos mensuales o anuales; requieren procesados de datos pesados; amplio uso en momentos concretos o se generan con procesos periódicos. Al unir todas las piezas del puzzle vemos de forma clara la solución: serverless.

Dependiendo del proveedor de cloud que utilicemos, o si utilizamos infraestructura propia, seleccionaremos la correspondiente alternativa para implementar la solución. Para este post, utilizaremos una solución basada en AWS. De modo que la arquitectura a alto nivel queda de la siguiente manera:

AWS Lambda  2

Para simplificar, en el diagrama anterior no se han incluido detalles de VPC, subredes, internet gateways, etc.

Los pasos que seguiremos serán los siguientes:

  1. Creación de plantilla IaC para:
  1. Creación del código Java para nuestra función lambda.
  2. Creación de informe/plantilla JasperReports.
  3. Subir nuestros recursos (plantilla Cloudformation, función lambda y plantilla JasperReports) a AWS.
  4. Realizar pruebas de nuestra función lambda.

El código fuente utilizado en este post, se localiza en el siguiente repositorio de Github.

Este proyecto se ha basado en otro ya existente en el Github de AWS.

El proyecto original contiene el código de una lambda que genera el informe JasperReports a partir de una conexión a una base de datos RDS. Ha servido gran parte del código, pero se ha modificado la forma de trasladar los datos a la plantilla. Para hacerlo más genérico y reutilizable, en vez de recuperar los datos desde una consulta a base de datos, vamos a procesar el JSON que viaja en la petición POST.

NOTA: antes de nada, destacar que no va a mostrarse el código completo sino que se explicarán las principales partes más interesantes.

Paso 1 - Creación de plantilla IaC

Una forma sencilla para crear todos los elementos de infraestructura que necesitamos para implementar nuestra funcionalidad es utilizar una plantilla de infraestructura como código, IaC. Cada proveedor cloud tiene su propio servicio de IaC, hay soluciones agnósticas al proveedor Cloud, por ejemplo Terraform, y en el caso de AWS este servicio se denomina CloudFormation.

Ya que no es el objetivo de este post analizar en detalle IaC, por lo que sólo vamos a explicar algunos parámetros del fichero de CloudFormation en el que definimos el stack. El fichero está localizado aquí.

En el fichero se han definido varios parámetros para poder configurar el stack a la hora de lanzarlo, que son:

También se define la función javaJasperLambda en la que destacar los siguientes puntos:

Por otro lado, también definiremos el swagger mínimo de nuestra función para que pueda interpretarlo correctamente el API Gateway y vincule con la invocación a nuestra función.

En este fichero, además de definir la API, se incluye la referencia a la función que se invocará al recibir el API Gateway peticiones en la URL en la que se publica la API. Concretamente, en esta sección URI. que tiene el siguiente valor:

"arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:java_jasper/invocations"

Donde son destacables:

Paso 2 - Creación del código Java para nuestra función lambda

En el paso anterior hemos definido los elementos que requerirá nuestra función, que no varían mucho de cualquier otra. Ahora nos centraremos en crear nuestra función que sea capaz de:

Aunque se ha tomado de base el código de ejemplo de AWS, se ha modificado el método que procesa la entrada, para recuperar el cuerpo de la petición POST (postBody) y qué plantilla (template) debemos utilizar.

JasperReports requiere que la información que se traslada a los informes sean objetos Java, con su correspondiente tipo de dato. De modo que esta información es necesaria incluirla dentro del body que recibe nuestro endpoint, quedando de la siguiente manera:

"title": "<Título del informe>",
"parameters": {
    "parametro_n": {
        "type": "<Tipo de dato del parámetro>",
        "value": "<valor del parámetro>"
    }, ...

Los tipos de datos de los parámetros son los siguientes (aunque pueden definirse más):

Los registros de un datasource se incluirán de la siguiente manera:

"parameters": {
  "parametro_n": {
    "type": "<Datasource ó MainDatasource>",
      "value": [
        {
          "field_n": {
            "type": "<Tipo de dato del campo>",
            "value": "<valor del campo>"
          }, ...
          ] ...

De este modo, se han realizado grandes cambios en la clase que genera el informe para trasladar el cuerpo JSON de la llamada POST a una estructura de parámetros y objetos que entienda JasperReports.

En primer lugar, se recorren todos los elementos que vienen en el JSON. Y luego, se van añadiendo a un mapa de parámetros según el tipo concreto de cada uno.

Sobre los data sources, ya se pasen como parámetros o el principal del informe, JasperReports requiere objetos del tipo JRDataSource. Hay varias clases que implementan dicha interfaz, entre ellas JREmptyDataSource, que es un data source vacío. Otra implementación es JRMapCollectionDataSource, que utiliza una colección de mapas (clave y valor) y es la que vamos a utilizar en este post. Cada elemento/mapa de la colección corresponde con un registro del data source.

Otro cambio importante que se ha realizado con respecto al repositorio de ejemplo de AWS es sustituir el uso de la Java SDK1 por la versión SDK2. La causa principal de este cambio ha sido la mejora del rendimiento de la nueva versión y el tener un mayor soporte a futuro. Tras este cambio, junto con la ampliación de memoria a 2048MB, se ha logrado un tiempo de respuesta de unos 10 segundos incluyendo el arranque en frío (cold start). Con la versión anterior de la SDK y 512MB de memoria, API Gateway muchas veces daba error de timeout en la primera llamada por tardar más de 30 segundos en hacer el arranque en frío. En las siguientes llamadas (una vez está la lambda cargada) los tiempos varían en función de la complejidad del informe, pero no existen problemas de timeout con el API Gateway. En cualquier caso, siempre se han conseguido mejores resultados con la SDK2.

Paso 3 - Creación de informe/plantilla JasperReports

El siguiente punto consiste en elaborar una plantilla con la que poner a prueba nuestro desarrollo. Los que han tenido contacto con JasperReports hace unos años recordarán el editor iReport. Sin embargo, a día de hoy, ha evolucionado en otra herramienta: Jaspersoft Studio. Aunque está basada en el IDE Eclipse, se instala como una herramienta totalmente independiente.

Crearemos nuestra plantilla con los siguientes parámetros de entrada:

AWS Lambda  3

En este caso el datasource lo vamos a pasar por parámetro. También podría pasarse como data source principal en cuyo caso deberíamos definir los campos que forman parte del data source. En nuestro ejemplo, los definiremos en un dataset Dataset1:

Por último, establecemos el diseño de nuestra plantilla utilizando los campos que hemos definido anteriormente.

En el título incluímos todos los campos que pasamos por parámetros salvo datasource1. Al arrastrar cada uno, incluye también una caja de texto con cada nombre, para localizarlos mejor. En el caso de la imagen, establecemos como expresión el valor del parámetro test_image.

AWS Lambda  4

Para terminar, en la sección de detalle incluimos una tabla. Al editar el detalle de la misma, accedemos a definir un subreport en el que incluir como columnas cada uno de los campos del Dataset1 definido previamente.

AWS Lambda  5

Con todo, ya tenemos definido un informe con el que poder probar nueva función lambda. Podemos localizar la plantilla de ejemplo en el repositorio.

Paso 4 - Subir nuestros recursos a AWS

Una vez tenemos listo el fichero de CloudFormation, la definición de la API, el código fuente de la función lambda y la plantilla JasperReports, lo siguiente es incorporarlo todo a AWS. Lo ideal es incorporar todos estos recursos utilizando nuestros sistemas de CI/CD (Jenkins, Gitlab-CI, etc.), modificando las correspondientes plantillas o pipelines.

Como primera aproximación, e independiente de la solución CI/CD, vamos a utilizar unos scripts de shell y Docker para automatizar todo el proceso. Todos los ficheros necesarios están ubicados en el repositorio del proyecto.

Los requisitos son simples: tener una cuenta de AWS y Docker instalado en nuestra máquina local. Se ha elegido Docker para simplificar todo el proceso y no tener que instalar Java, Maven ni AWS-CLI en nuestro equipo local.

Los pasos son los siguientes:

AWS Lambda  6

En el anterior diagrama se muestra el flujo utilizado por los scripts:

  1. Compilar y empaquetar el código de la función lambda.
  2. Crear los buckets S3 para los ficheros de la función lambda y las plantillas JasperReports.
  3. Copiar todos los recursos a los buckets S3:
  1. Lanzar la creación del stack de CloudFormation, a partir del fichero existente en el bucket S3, estableciendo los correspondientes valores a los parámetros (nombre de los buckets y fichero empaquetado) según la configuración establecida en lambda_config.sh.
AWS Lambda  7

NOTA: En el stack existen referencias al resto de ficheros ubicados en los buckets S3 (swagger y empaquetado con la función) que se utilizarán a la hora de crear los recursos.

Paso 5 - Realizar pruebas

Como último paso, utilizaremos Postman para realizar una llamada POST al endpoint que hemos publicado en el API Gateway y que invoca a nuestra función lambda.

Lo primero que debemos hacer es obtener la URL en la que está “esperando” nuestra lambda. Para ello, vamos a la consola Web de AWS, concretamente al API Gateway y vemos qué URL se le ha asignado.

AWS Lambda  8

De este modo, la llamada que debemos hacer para invocar nuestra función lambda es:

Siendo el parámetro “template” el nombre del fichero que contiene la plantilla que deseamos utilizar y que está ubicada en el bucket S3. Y el cuerpo del mensaje, incluimos todos los datos que deseamos utilizar en nuestro informe.

En el repositorio hay una llamada de ejemplo para trasladar los datos a la plantilla JasperReports de prueba que hemos creado anteriormente. Es un fichero con una colección de postman en lo que lo único que debemos modificar es la URL en la que está la función.

Podemos revisar la estructura del mensaje, en el que para cada campo se indica su tipo y el valor que toma.

AWS Lambda  9

Una vez responda el servicio, salvamos la respuesta como un fichero PDF y lo abrimos para verificar que ha funcionado correctamente.

AWS Lambda  10

NOTA: Hay que tener en cuenta que la primera vez, o cuando lleva tiempo sin utilizarse la lambda, AWS debe hacer el arranque en frío y tardará algo más en responder el servicio.

Cierre

Hemos visto cómo crear un sistema genérico de generación de informes utilizando JasperReports y AWS lambda. Hemos utilizado infraestructura como código (IaC), definición de APIs, procesado el JSON de una llamada POST desde Java, mejorado el rendimiento y migrado desde la SDK1 a la SDK2, creado una plantilla JasperReports y automatizado de manera sencilla todo el proceso de construcción y despliegue utilizando scripts de shell y Docker.

Los siguientes pasos consistirán en comprobar que el servicio acepta las plantillas ya existentes y se pueden generar los informes sin problema. Esto nos permite una primera vía para comenzar con la transformación digital de los clientes, permitiendo que sigan utilizando las plantillas que ya tienen pero ofreciendo un nuevo uso de las mismas e iniciarlos en el camino de la computación en la nube.

Como reflexión podemos decir que transformar no es siempre tirarlo todo y empezar desde cero. Buena parte del trabajo consiste en buscar nuevas formas de hacer las cosas y reutilizar al máximo lo que ya está funcionando correctamente.

Referencias

Cuéntanos qué te parece.

Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.

Suscríbete