Cambiando el chip hacia event-driven

Ni falta hace que escriba sobre las ventajas que proporciona el paradigma event-driven en el que los componentes de software reaccionan a eventos a los que se han suscrito previamente. El productor está desacoplado del consumidor y se pueden añadir consumidores conforme se necesitan nuevos tratamientos del evento.

Además, el dominio de los eventos va a crecer y dotar de más significado al código: Se puede tener un evento UserToPurchaseInsurance y un evento InsurancePurchased que semánticamente es más significativo que tener un objeto Insurance con un malvado campo status.

descarga

Event-driven sin broker de mensajería parece un poco engorro. Sin embargo, Spring facilita mucho el trabajo ya que despliega una infraestructura de publicación y suscripción a eventos para los beans registrados en el contexto. Si se da el caso de trabajar en un monolito sin broker de mensajería, los eventos de aplicación de Spring pueden ser un buen inicio para refactorizar algunas partes del código a una arquitectura orientada a eventos.

Este código incluiría 4 dependencias:

  • al proveedor de seguros,
  • al servicio que envía emails,
  • al repositorio y
  • al servicio que roba el coche asegurado:
if (insuranceProvider.purchase(insurance)) {
    sendEmail(insurance);
    persist(insurance);
    stealCar(insurance);
}

Podría convertirse en este otro código que tendría como dependencias el proveedor de seguros y el publicador de eventos. Como se indicó anteriormente, desacoplando productor de los N consumidores.

if (insuranceProvider.purchase(insurance)) {
    publishEvent(InsurancePurchasedEvent.from(insurance));
}

Los puntos de entrada de los consumidores en este caso serían tres listeners:

@Component
public class InsurancePurchasedEmailEventListener {
    @EventListener
    public void sendAnEmail(InsurancePurchased insurancePurchased) {}
}
@Component
public class InsurancePurchasedPersistenceEventListener {
    @EventListener
    public void persistInsurance(InsurancePurchased insurancePurchased) {}
}
@Component
public class InsurancePurchasedThiefEventListener {
    @EventListener
    public void stealTheCar(InsurancePurchased insurancePurchased) {}
}

Esta entrada surge tras haber leído el artículo Spring Events de Baeldung en el que explican exactamente cómo publicar el evento y recuerdan que por defecto los eventos son recogidos por los listeners en el mismo hilo, por lo que la ejecución no continuará hasta que se procese el evento por todos los listeners. Este comportamiento se puede alterar definiendo un bean ApplicationEventMulticaster.

Introducción a Spring Batch. Toma 2

Fuente: spring.io

 

Esta es la segunda parte de Introducción a Spring Batch. Toma 1 donde se explicaron algunos conceptos básicos sobre jobs de Spring Batch y se describió lo mínimo necesario para usar Spring Batch en una aplicación. ¡Continuamos!

 

5. Transaccionalidad

Un step se puede declarar como tasklet-oriented o chunk-oriented:

  • Step tasklet-oriented: Podemos definir una tarea simple con el método tasklet() que se caracteriza porque se crea una transacción por cada elemento. En caso de fallo de escritura, la transacción se revierte.
  • Step chunk-oriented: Un chunk es un conjunto de elementos que se leen y se procesan por separado pero se persisten juntos. El número de elementos por chunk se indica en la definición del step con el método chunk(). Con este tipo de steps, se crea una transacción por cada chunk.

Nos podría interesar elegir procesamiento chunk-oriented para maximizar el uso de la RAM o beneficiarnos de la transaccionalidad (por ejemplo, haciendo rollback de la escritura de 10 elementos que deberían ir juntos). Por otra parte, también nos podría interesar escribir un conjunto de elementos a la vez (p. ej, enviar a un endpoint que recibe un array de elementos).

Como siempre, con Spring Boot no es necesario declarar el transactionManager ya que es inyectado automáticamente por el framework.

En el siguiente commit, se ha declarado un ItemReader que lee un archivo CSV y un ItemWriter que muestra cada objeto Persona. También se ha cambiado la definición del step para establecer un chunk de 2 elementos.

Las implementaciones de FlatFileItemReader, DelimitedLineTokenizer, DefaultLineMapper y FieldSetMapper ofrecidas por Spring nos ayudan a leer un CSV en unas pocas líneas.

 

6. Tolerancia a fallos

Los componentes ItemReader, ItemProcessor e ItemWriter lanzarán excepciones en caso de error. Podemos evitar que ante una excepción rompa la aplicación con el método skipLimit() indicando el número de excepciones a saltar. Además los tipos de excepciones tienen que definirse usando el método skip() (tantas veces como sea necesario).

Para trabajar con los elementos saltados (que han provocado una excepción) se puede implementar SkipListener. Un ejemplo sería imprimir la entrada errónea o guardarla en otro CSV.

En el tercer commit, se ha introducido un error de lectura “-“ en el CSV y un error de escritura (lanzando la excepción IllegalArgumentException). Se ha incluido el método faultTolerant() en el step con un límite de 2 excepciones FlatFileParseException, FlatFileFormatException e IllegalArgumentException.

Como el límite es 2, el job se ejecutará correctamente saltando los 2 errores introducidos. Como ejercicio, podríamos añadir otro error al archivo CSV y ver cómo falla el job.

 

7. Reintentos

Es posible reintentar una acción que ha fallado previamente (tanto la lectura como el procesamiento o la escritura). En el cuarto commit se ha añadido una llamada al método retryLimit(1) para reintentar las excepciones del tipo IllegalArgumentException.

Es conveniente utilizar un límite para los reintentos ya que es posible que la misma excepción continúe sucediendo cada vez que se ejecute el mismo proceso.

 

8. Logs y estadísticas

El método StepBuilderFactory#listener() recibe cualquier tipo de listener como por ejemplo ChunkListener, StepListener, RetryListener… Todos estos pueden utilizarse para registrar el progreso (escribir logs) de un step. Como apoyo para escribir estos logs, podemos utilizar el contexto del step o del job para guardar timestamps o cualquier otro tipo de información.

Centrándonos en registrar el progreso de un job, definiendo JobBuilderFactory podemos usar el método .listener() pasando como argumento una instancia de JobExecutionListener.

La clase StepExecution tiene un método getSummary() que devuelve cierta información tras la ejecución del step, como por ejemplo:

El campo status devuelve un valor del enumerado BatchStatus y puede ser mapeado al código de finalización de la aplicación.

El campo exitStatus devuelve un valor del enumerado ExitStatus y representa el estado de finalización del step. Este valor no siempre es equivalente al valor del campo status. Además es posible personalizarlo para proporcionar más información y con ello, por ejemplo, definir un flujo condicional en el que el inicio de un step dependa del valor del campo exitStatus del step anterior.

En cualquier caso, Spring Batch permite definir flujos de steps donde un step se ejecuta  de manera condicional teniendo en cuenta el estado de finalización del step anterior.

En este commit se han definido unos cuantos listeners para registrar cada acción ocurrida durante el procesamiento batch. Además se ha añadido un atributo “timing” al contexto del chunk para medir el tiempo transcurrido durante el procesamiento de cada chunk.

 

9. Parando y re-arrancando un proceso batch

Spring Batch nos proporciona el bean JobOperator con los siguientes métodos:

  • getRunningExecutions(“jobName”) que devuelve una lista de ids de los jobs que se están ejecutando.
  • stop() que para la ejecución del step en cuanto se termina de ejecutar el código escrito en el mismo.
  • restart() que continúa la ejecución a partir del siguiente step.

 

10. Administración web

El proyecto Spring Cloud Data Flow proporciona una interfaz web y una CLI para administrar los jobs y streams que proceden de los proyectos Spring Cloud Task y Spring Cloud Stream. El objetivo del proyecto Spring Cloud Task es el de integrar los jobs de Spring Batch como microservicios en la nube.

Para convertir un proyecto en Spring Batch a uno de Spring Cloud Task únicamente es necesario añadir la dependencia y la anotación @EnableTask.

 

Aquí podemos encontrar los pasos necesarios para ejecutar un job dentro del Spring Cloud Data Flow:

Lo más común es registrar la aplicación con la URI de Maven (maven://) para que Spring Cloud Data Flow se descargue el artefacto del repositorio. Otros tipos de aplicaciones que se pueden registrar son sinks y sources.

Podemos consultar las estadísticas de los jobs accediendo a: http://localhost:9393/dashboard/index.html#/jobs/executions/1

Y las estadísticas de los steps en: http://localhost:9393/dashboard/index.html#/jobs/executions/1/1

Sin duda merece la pena darle una oportunidad a Spring Cloud Data Flow, tiene cosas muy chulas como por ejemplo, una herramienta visual para pintar el flujo de datos entre los distintos microservicios.

Es posible definir el directorio en el que se almacenarán los logs de la aplicación. No obstante, aunque no especifiquemos ninguna ruta, en el log de Spring Cloud Data Flow podremos distinguir una primera línea que comienza con “Logs will be in ” indicando la carpeta temporal elegida por Spring para este propósito.

En este commit podemos ver lo fácil que es convertir un proyecto Spring Cloud Data Flow:

 

11. Particionamiento

Existen diferentes estrategias para escalar un proceso batch ya sea de manera multihilo o multiproceso.

El particionamiento de un step es una de ellas y permite ejecutar steps en máquinas remotas o en hilos locales. Consiste en definir un step maestro (master step) que delegará el trabajo particionado en steps esclavos (slave steps). Para usar el particionamiento de steps es necesario definir tanto la estrategia de particionado como los esclavos.

El step maestro utilizará una implementación de Partitioner para escribir en el contexto de ejecución toda la información que necesitará cada esclavo para procesar su partición de datos.

En este último commit se ha añadido un segundo archivo CSV y se ha definido un bean CustomMultiResourcePartitioner que utilizará un step maestro para escribir el nombre del archivo CSV en cada contexto. Cada esclavo recibe un contexto diferente por parte del maestro.

Los dos esclavos se ejecutan en hilos diferentes porque se ha declarado un taskExecutor con el DSL del StepBuilderFactory.

Además, se ha utilizado la anotación @StepScope para recuperar el nombre del archivo guardado en el contexto usando el Spring Expression Language.

 

12. Extra:

Spring Batch proporciona múltiples implementaciones para facilitar el desarrollo de procesos batch. Aquí puedes encontrar una lista de ellas:

View at Medium.com

Esta segunda toma de la introducción a Spring Batch es una traducción del artículo publicado en el blog de Fintonic Product Team.

Integrando un captcha en un proyecto Java con Spring

Hoy me ha tocado implementar reCAPTCHA en el servidor con Spring. En el ejemplo incrustan el código Java directamente en el JSP, cosa que intento evitar por todos los medios, así que he creado un bean Captcha desde el que hacer uso de la librería.

Igual que hay días que Spring los fastidia completamente, hoy ha sido uno de esos días en que cada problema que he tenido lo he solucionado mágicamente con el framework.

El primero:

Usamos el archivo captcha.properties para almacenar la clave privada y la pública. Este archivo no lo añadimos al control de versiones y así evitamos que las claves viajen por donde no tienen que viajar.

Diagrama para incrustar un captcha en un proyecto con Spring

El bean Captcha recibe estas claves en el constructor para que se vea claramente qué necesita para funcionar. El problema fue cómo pasar las claves desde el archivo de propiedades al constructor. Desde el applicationContext.xml ha sido muy fácil definiendo:

<bean class="org.springframework.beans.factory
   .config.PropertyPlaceholderConfigurer">
   <property name="location">
      <value>classpath:captcha.properties</value>
   </property>
</bean>

Y usando la propiedad que está dentro del archivo en cualquier parte del applicataionContext así:

${captcha.public}

Ahora, el problema de abrir un archivo de texto y extraer una propiedad de él es de Spring.

El segundo:

Desde un controlador, pasar a la vista JSP -del formulario- el código HTML para cargar el captcha. Por arte de magia todos los métodos con @RequestMapping pueden recibir un mapa de atributos, de manera que en el controlador se pueden añadir los atributos que se necesiten:

@RequestMapping(value = "/", method = RequestMethod.GET)
public String home(ModelMap attributes) {
   attributes.addAttribute("captcha",
      captcha.getHtmlCaptcha());
   return "index";
}

En index.jsp, el código HTML dentro del atributo se incrusta así:

<c:out value="${captcha}" escapeXml="false" />

 

El tercero:

Al registrarse en reCAPTCHA, es necesario introducir la dirección del servidor. La misma que hay que enviar con cada intento de resolver el captcha. Pero ¿Cómo obtener la dirección remota desde un controlador en Spring? En esta ocasión también se puede añadir mágicamente a los métodos @RequestMapping el argumento HttpServletRequest request que ya se encarga de asignar internamente el framework y posteriormente se llama a

request.getRemoteAddr()

Ayer vi la primera parte de los vídeos que describen la cultura de los equipos de trabajo en Spotify. Me encantó el vídeo: Describe poco a poco cómo funcionan los equipos y justifican estas decisiones con su filosofía, que está muy inspirada en el manifiesto ágil.

Una de las muchas cosas que aprendí fue que prefieren integrar nuevas características en cuanto se desarrollan aunque las integren desactivadas. Así, detectan cuanto antes fallos de integración.

Mi manera de integrar el captcha desactivado ha sido incluir una propiedad enabled en el archivo captcha.properties pero estoy seguro de que hay una forma mejor: Una infraestructura para habilitar y deshabilitar desde un mismo lugar muchas de las características de la aplicación por diferentes que sean. ¿Cómo lo hacéis vosotros?