Introducción a Spring Batch. Toma 1

1. ¿Qué es un proceso batch?

Un proceso batch es una tarea automática para procesar un gran de volumen de datos. Este proceso se puede ejecutar sin interacción humana y repetirse periódicamente.

Qué no es un proceso batch: no es una tarea programada (un cron). Es bastante común programar un proceso batch, pero no es necesario hacerlo.

2. ¿Qué debemos tener en cuenta con un proceso batch?

  • Transaccionalidad porque queremos hacer roll back cuando los datos han sido invalidados.
  • Tolerancia a fallos porque no queremos que la aplicación termine cuando ocurra una excepción.
  • Reintentos porque… sometimes shit happens.
  • Logs y estadísticas porque de vez en cundo necesitamos saber qué ocurre dentro del proceso batch.
  • Parada y arranque de los procesos batch.
  • Administración web porque mola.
  • Particionamiento porque el trabajo puede ser compartido entre diferentes máquinas.

Cada uno de estos aspectos será comentado más tarde. He preparado unos cuantos commits en un repo para repasar todos estos puntos mostrando las diferencias en el código.

3. Conceptos de Spring Batch

Spring Batch es un proyecto que nos proporciona un framework para desarrollar aplicaciones batch. Este proyecto tiene un largo recorrido y es el resultado de la contribución de varias empresas con mucha experiencia en procesamiento batch.

Jobs, Steps, ItemReader, ItemProcessor and ItemWriter:

Fuente: Spring Batch Reference Documentation https://docs.spring.io/spring-batch/trunk/reference/html/domain.html

En Spring Batch se ejecutan jobs que se dividen en steps. Cada step tiene un componente para obtener los objetos (ItemReader), otro para procesarlos (ItemProcessor) y uno más para persistirlos (ItemWriter). El componente de procesamiento de datos es opcional.

Normalmente necesitaremos una instancia de JobBuilderFactory para declarar el Job y un StepBuilderFactory  para declarar el Step. No hay problema, Spring nos proporciona ambas.

JobExecution y JobLauncher

Un job puede ejecutarse por una instancia de JobLauncher. Durante su ejecución la información es almacenada y compartida en el JobExecutionContext y el StepExecutionContext.

El JobLauncher devuelve un JobExecution que nos da información sobre la ejecución, como por ejemplo el resultado de la misma: COMPLETED, FAILED…

JobInstance, JobParameters y RunIdIncrementer

JobInstance es la combinación de un Job y sus JobParameters. Una de las reglas de Spring Batch es que no se puede volver a ejecutar un Job si su JobExecution tiene estado COMPLETED. Sin embargo, se puede utilizar un RunIdIncrementer para ejecutar el mismo job varias veces ya que éste modifica internamente sus parámetros.

JobRepository

Spring Batch gestiona por sí sólo una base de datos con información sobre la ejecución de los jobs instanciando un bean de JobRepository. Para ello sólo necesitamos declarar la dependencia de H2 en el entorno de desarrollo.

4. Requisitos mínimos para comprobar que todo esto funciona

  • Dependencia de Spring Boot Starter Batch (aquí es donde reside toda la magia).
  • Dependencia del driver de la base de datos.
  • Anotación @EnableBatchProcessing en la clase de configuración de Spring.
  • Bean que define del job.
  • Bean que define el step con una tarea que escriba un mensaje “Reading…”. Cuando esa tarea devuelva un null indicará el fin de la fuente de datos y por tanto que el job ha terminado.
@Bean
public Job job(Step step1) throws Exception {
    return jobBuilderFactory.get("job1")
        .incrementer(new RunIdIncrementer())
        .start(step1)
        .build();
}

@Bean
public Step step1() {
    return stepBuilderFactory.get("step1")
        .tasklet(new Tasklet() {
            public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) {
                System.out.println("Reading...");
                return null;
            }
        })
        .build();
}

Puedes clonar el repo que he preparado para este post y ver el código añadido en el primer commit para ejecutar Spring Batch con lo mínimo necesario.

Este artículo es una traducción del que escribí hace unos meses en el blog de Fintonic Engineering.

Desactivar autoconfiguración de Mongo en tests de integración con Spring Boot

Puede interesar, escribiendo un test, querer levantar el contexto de Spring sin instanciar ciertos componentes como la capa de acceso a datos. De esta manera, se prueba cierta integración sin pasar horas definiendo mocks.

Spring Boot utiliza la propiedad spring.autoconfigure.exclude para excluir clases @Configuration y no instanciar los beans declarados en su interior. Se puede definir esta propiedad para desactivar la autoconfiguración de Spring Boot Data Mongo. Las clases que instancian los componentes necesarios para utilizar una base de datos Mongo son:

– org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration
– org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration

Se puede utilizar la anotación @TestPropertySource para inyectar las propiedades al contexto de Spring que se levanta al ejecutar el test con la anotación @SpringBootTest.

Por último, habría que inyectar un mock de mongoTemplate a los componentes que lo usen.

Y para muestra, un ejemplo insultántemente tonto:

@RunWith(SpringRunner.class)
@SpringBootTest
@TestPropertySource(properties = "spring.autoconfigure.exclude=
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration")
public class DemoApplicationTests {

    @Configuration
    static class TestConfiguration {
        @MockBean private MongoTemplate mongoTemplate;
    }

    @Test
    public void integrationTestNotNeedingRepository() {
        Assert.assertTrue(true);
    }
}

La magia de Spring Tool Suite

Siguiendo mi entrada anterior en la que comentaba algunas de las características de Spring Boot que son pura magia, hoy quiero destacar algunas de las funcionalidades de Spring Tool Suite, la distribución de eclipse desarrollada para el ecosistema Spring.

En esta charla de la SpringOne llamada Next Level Spring Boot Tooling, los ponentes completan y corrigen una aplicación Spring Boot que posteriormente ejecutan en local y en la plataforma Cloud Foundry.

Entre otras características, destacan cómo Spring Tool Suite sugiere, tras escribir el operador new de Java, las implementaciones encontradas para el tipo de la referencia que precede en la sentencia.

Una de las correcciones incluida en las últimas versiones de STS ha sido la de permitir duplicar y alterar la configuración de una aplicación para poder ejecutar desde el IDE varias instancias de ésta y probar la capacidad de escalado horizontal del servicio. Configurando el puerto con 0, el framework Spring Boot asignará un puerto aleatorio (después de arrancar) para la instancia, permitiendo así arrancar varias. Esto es muy adecuado para llevar a cabo las pruebas aunque en un sistema con contenedores -si fuese el caso-, las instancias expondrían el mismo puerto y se ejecutarían en diferentes IPs.

Quizás en otros IDEs ya está disponible tras definir un campo de una clase la posibilidad de añadir -como quick fix- la anotación @Autowired, sin embargo merece la pena destacarlo porque es bastante útil por la cantidad de beans que se inyectan.

La segunda parte de la charla repasa la integración de la herramienta con el PaaS Cloud Foundry y describen varias características, entre ellas cómo, tras desplegar una aplicación en la nube y haber modificado varias veces la configuración de ésa, STS compara la configuración local y la remota mostrando las diferencias para que puedan ser modificadas y guardadas. Siempre, a la hora de desplegar una aplicación necesitamos cambiar una configuración en el último momento. Una gran funcionalidad muy útil e inteligente que nos proporciona el equipo de Spring Tool Suite.

Por último, muestran cómo depurar de una forma más sencilla el conjunto de la aplicación registrando una instancia local contra el discovery service en Cloud Foundry (Eureka, por ejemplo).

Estas han sido algunas de las bondades que me han impresionado de la magia de STS pero hay muchas más por descubrir en el vídeo.

Spring Boot es magia

No se puede negar que Josh Long y la gente de Spring están luchando por cambiar la mentalidad de los que piensan o hemos pensado que Spring Boot hace demasiada magia, de la negra, de la que luego no te explican cuál fue el truco.

El framework Spring Boot basa su filosofía en (entre otras cosas) una cantidad ingente de condiciones que evalúan, por ejemplo, si hay cierta clase en el classpath y en caso de que exista dicha clase, instanciar beans de configuración para ahorrarnos ese trabajo. De esta manera, al arrancar y detectar clases de MySQL en el classpath (porque se añadió la dependencia), Spring Boot evalúa las condiciones resultando en la instanciación de beans de configuración como dataSource y jdbcTemplate. Spring Boot supone -y lo hace bien- que necesitaremos esos beans porque para algo hemos incluido la dependencia.

Spring Boot también hace magia con las propiedades, ya sean a través de variables de entorno, argumentos en la línea de comandos, en un archivo YAML o en un archivo .properties. Las clases de Spring Boot se autoinyectan estas propiedades que escribimos para autoconfigurarse y, de nuevo, ahorrarnos clases de configuración.

Gracias a esto, basta sólo con añadir la propiedad spring.cloud.config.uri=http://localhost:3334 al application.properties de un proyecto Spring Boot y añadir la dependencia spring-cloud-config-client en el POM. Al arrancar este proyecto, la aplicación intentaría conectarse con un servidor de gestión de configuración que escucharía en el puerto 3334 y descargaría su configuración de la URL http://localhost:3334/application/default si el nombre de la aplicación es application (Propiedad spring.application.name).

No hace magia, es convención sobre configuración y mucho trabajo por debajo, pero a veces las cosas no fluyen como uno quiere. En más de una ocasión y en más de dos, sí que ha arrancado pero no me ha funcionado una aplicación por falta de configuración ya que no se ha podido conectar al servidor de configuración sin la dependencia spring-cloud-config-client en el POM ¿Lógico, verdad? Pero estos olvidos ocurren y el eclipse todopoderoso no pudo ayudarme a recordar añadir la dependencia.

Tanta autoconfiguración nos lleva a otro tipo de errores más escondidos y con mensajes de error más confusos con los que hay que tener más cuidado, completamente al revés de la filosofía fail-fast con la que se prefiere que los errores aparezcan en etapas tempranas para que puedan ser corregidos antes de que suponga algún tipo de pérdida en el momento en que aparezca.

Afortunadamente los IDEs nos siguen ayudando en la medida que pueden, en este caso eclipse no, pero sí Spring Tool Suite que provee autocompletado para archivos de propiedades y sólo sugiere las propiedades que inyectarían las dependencias incluidas en el POM.