Costuma criar aplicações em lote em Java? Pois, saiba que além de ter uma especificação para tratar essas aplicações, há um framework ótimo para criar e padronizar o código dos mesmos, o Spring Batch! Nesse artigo vou colocar umas pequenas dicas que podem servir para outras pessoas assim como me ajudaram muito.
“É fazendo que se aprende a fazer aquilo que se deve aprender a fazer.”
– Aristóteles
Primeira dica: Spring Batch pode ou não rodar com o Spring Boot
O Spring Boot facilita a configuração de projetos Spring e muitas vezes é fácil ter impressão que todos os projetos precisam rodar com ele, mas isso é falso. Apesar de particularmente recomendar o uso do Spring Boot, o Spring Batch é um framework independente, construído para ajudar na construção de aplicações batch mais robustas, sejam elas web ou não.
Para alcançar o objetivo a ferramenta disponibiliza um modelo de funcionamento que visa padronizar o código implementado para a aplicação. Nada melhor do que a documentação para explicar: Spring Batch Introduction
Segunda dica: iniciar uma aplicação Spring Batch pela linha de comando
O Spring Batch não foi construído para trabalhar como um gerenciador de execução e sim para ser chamado por um, no ambiente que trabalho, o cliente utiliza o programa Control-M para agendar e executar processamentos, sendo assim a aplicação deve ser executada a partir de uma linha de comando. Existem várias abordagens para conseguir isso no Spring Batch, aqui vou exemplificar a que mais achei simples.
- configure o projeto com o Spring Boot;
- na classe de execução (anotada com
@SpringBootApplication
) implemente a interfaceCommandLineRunner
; - em
application.properties
defina a propriedadespring.batch.job.enabled=false
(vai impedir que todos os jobs sejam executados automaticamente); - implemente o código de execução do job no método
run()
.
Exemplo:
public static void main(String[] args) {
System.exit(SpringApplication.exit(SpringApplication.run(SpringBatchDicasApplication.class, args)));
}
@Override
public void run(String... args) throws Exception {
log.info("Executando.");
Job job = appContext.getBean("importUserJob", Job.class);
JobParameters params =
new JobParametersBuilder().addString("importUserJob", String.valueOf(System.currentTimeMillis())).toJobParameters();
jobLauncher.run(job, params);
}
Terceira dica: separando a fonte de dados do JobRepository
do resto da aplicação
Quando a classe de configuração é anotada com @EnableBatchProcessing
o Spring Boot já configura um objeto JobRepository
com o datasource configurado pelo
usuário, mas e quando não quiser persistir as informações de execução na base principal? As vezes a informação da execução não vai ser utilizada, ou em casos em
que a execução é controlada externamente não é necessário persistir, nesses casos é possível definir outro datasource para o JobRepository.
Em muitos exemplos que achei é utilizado o MapJobRepositoryFactoryBean
, mas é um erro! Essa classe é para usar em ambientes de desenvolvimento, pois não é thread safe e pode apresentar problemas em ambiente produtivo, uma melhor alternativa é utilizar um banco em memória (como H2, HSQL ou Derby) que lida melhor com transação e threads.
Para configurar bancos diferentes:
- criar um bean de
BatchConfigurer
na classe de configuração; - criar um bean para o datasource do banco que se deseja para o
JobRepository
e anotá-lo com@BatchDataSource
; - como não há mais um datasource padrão, é necessário criar um bean para
DataSourceProperties
(responsável por obter os dados de configuração no application.properties), o bean primario com o datasource principal e outro do tipoJdbcTemplate
.
Trecho do código:
@Bean
@Primary
@ConfigurationProperties("spring.datasource")
public DataSourceProperties dataSourceProperties() {
return new DataSourceProperties();
}
@Bean
@Primary
@ConfigurationProperties("spring.datasource.configuration")
public HikariDataSource dataSource(DataSourceProperties properties) {
return properties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
@Bean
public JdbcTemplate jdbcTemplate(@Autowired DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
/**
* Datasource para o repositório do batch
*/
@Bean
@BatchDataSource
DataSource batchDataSource() {
return new EmbeddedDatabaseBuilder().addScript("classpath:org/springframework/batch/core/schema-drop-hsqldb.sql")
.addScript("classpath:org/springframework/batch/core/schema-hsqldb.sql").setType(EmbeddedDatabaseType.HSQL).build();
}
@Bean
public BatchConfigurer batchConfigurer() {
return new DefaultBatchConfigurer() {
@Override
public PlatformTransactionManager getTransactionManager() {
return new ResourcelessTransactionManager();
}
@Override
protected JobRepository createJobRepository() throws Exception {
JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
factory.setDataSource(batchDataSource());
factory.setTransactionManager(getTransactionManager());
factory.afterPropertiesSet();
return factory.getObject();
}
};
}
Quarta dica: tratando os erros com exit codes
Ao executar uma aplicação em lote via agendadores, alguns utilizam o exit code da execução para alarmes de erros. No Spring Batch é fácil configurar uma manipulador que traduz os erros para códigos de erro, segue um exemplo simples:
@Bean
ExitCodeExceptionMapper exitCodeToexceptionMapper(){
return exception -> 1;
}
O trecho implementa a interface funcional ExitCodeExceptionMapper
o qual recebe um Throwable
e retorna o código equivalente.
Quinta dica: estrutura do projeto
Após ler sobre a arquitetura do Spring Batch, e alguns projetos criados, o conceito de package by feature foi o que se encaixou melhor na maior parte dos projetos batch que construí, não é intenção desse artigo entrar no detalhe, mas posso sugerir o artigo https://phauer.com/2020/package-by-feature/ e o http://www.javapractices.com/topic/TopicAction.do?Id=205 sobre o assunto.
Por ser muito flexível, há muitas maneiras de utilizar o modelo do Spring Batch, tento sempre deixar o ItemReader
com a responsabilidade de buscar os dados,
o Processor
irá transformar, executar regras ou tratar os dados e o ItemWriter
fica responsável por persitir o resultado.
Finalizando
Espero que esse artigo seja encarado como o que é, uma série de dicas que não são a bala de prata, mas que podem se encaixar em algum problema que algum leitor precise resolver. Sugestões e feedbacks serão bem-vindos!
Código
Fontes
- https://docs.spring.io/spring-batch/docs/4.3.x/reference/html/index.html
- https://docs.spring.io/spring-boot/docs/2.5.1/reference/htmlsingle/