Se você já trabalhou com sistemas financeiros, sabe que uma das tarefas mais comuns é lidar com tarefas agendadas: verificar vencimentos, processar cobranças, gerar relatórios diários. No mundo Spring Boot, muita gente recorre ao @Scheduled. No Quarkus, temos o Quarkus Scheduler — e ele é bem direto ao ponto.
Nesse post vou mostrar como usar o Scheduler num contexto real de fintech, sem aquele exemplo de "contador que incrementa a cada 10 segundos" que todo tutorial usa. Bora?
O que vamos construir
Uma API de cobranças com três jobs agendados:
- A cada 30 segundos: verifica vencimentos e atualiza status
- Todo dia às 8h: inicia o processamento das cobranças pendentes
- Todo dia às 18h: gera um relatório com totais
Estrutura do projeto:
src/main/java/dev/dellamas/fintech/
├── model/
│ └── Cobranca.java
├── service/
│ └── CobrancaService.java
├── scheduler/
│ └── CobrancaScheduler.java
└── resource/
└── CobrancaResource.java
Criando o projeto
mvn io.quarkus.platform:quarkus-maven-plugin:3.18.4:create \
-DprojectGroupId=dev.dellamas \
-DprojectArtifactId=quarkus-scheduler-fintech \
-Dextensions="rest,scheduler,rest-jackson" \
-DnoCode
O modelo: Cobranca.java
Vamos começar pelo modelo. Nada sofisticado aqui — uma cobrança com id, cliente, valor, vencimento e status. Mas já pensou quantas vezes você viu um sistema que não tem nem isso bem definido?
package dev.dellamas.fintech.model;
import java.math.BigDecimal;
import java.time.LocalDate;
public class Cobranca {
private String id;
private String cliente;
private BigDecimal valor;
private LocalDate vencimento;
private StatusCobranca status;
public enum StatusCobranca {
PENDENTE, PROCESSANDO, PAGA, VENCIDA
}
public Cobranca(String id, String cliente, BigDecimal valor, LocalDate vencimento) {
this.id = id;
this.cliente = cliente;
this.valor = valor;
this.vencimento = vencimento;
this.status = StatusCobranca.PENDENTE;
}
public String getId() { return id; }
public String getCliente() { return cliente; }
public BigDecimal getValor() { return valor; }
public LocalDate getVencimento() { return vencimento; }
public StatusCobranca getStatus() { return status; }
public void setStatus(StatusCobranca status) { this.status = status; }
}
Sem persistência por enquanto — o foco aqui é no Scheduler. Num projeto real, isso viraria uma entidade Panache sem dor.
O serviço: CobrancaService.java
Esse é o coração da lógica. Ele mantém as cobranças em memória e expõe os métodos que os jobs vão chamar. Perceba o ConcurrentHashMap — os jobs rodam em threads separadas, então se você não pensar em concorrência aqui, vai ter problema cedo ou tarde.
package dev.dellamas.fintech.service;
import dev.dellamas.fintech.model.Cobranca;
import dev.dellamas.fintech.model.Cobranca.StatusCobranca;
import jakarta.enterprise.context.ApplicationScoped;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
@ApplicationScoped
public class CobrancaService {
private final Map<String, Cobranca> cobrancas = new ConcurrentHashMap<>();
public CobrancaService() {
cobrancas.put("C001", new Cobranca("C001", "Empresa Alpha",
new BigDecimal("1500.00"), LocalDate.now().minusDays(1)));
cobrancas.put("C002", new Cobranca("C002", "Startup Beta",
new BigDecimal("890.50"), LocalDate.now()));
cobrancas.put("C003", new Cobranca("C003", "Loja Gamma",
new BigDecimal("3200.00"), LocalDate.now().plusDays(5)));
cobrancas.put("C004", new Cobranca("C004", "Consultoria Delta",
new BigDecimal("650.00"), LocalDate.now().minusDays(3)));
}
public List<Cobranca> listarTodas() {
return new ArrayList<>(cobrancas.values());
}
public List<Cobranca> listarVencidas() {
return cobrancas.values().stream()
.filter(c -> c.getVencimento().isBefore(LocalDate.now())
&& c.getStatus() == StatusCobranca.PENDENTE)
.collect(Collectors.toList());
}
public List<Cobranca> listarPendentes() {
return cobrancas.values().stream()
.filter(c -> c.getStatus() == StatusCobranca.PENDENTE)
.collect(Collectors.toList());
}
public int marcarVencidasComoVencidas() {
List<Cobranca> vencidas = listarVencidas();
vencidas.forEach(c -> c.setStatus(StatusCobranca.VENCIDA));
return vencidas.size();
}
public int processarPendentes() {
List<Cobranca> pendentes = listarPendentes();
pendentes.forEach(c -> c.setStatus(StatusCobranca.PROCESSANDO));
return pendentes.size();
}
public BigDecimal totalVencido() {
return cobrancas.values().stream()
.filter(c -> c.getStatus() == StatusCobranca.VENCIDA)
.map(Cobranca::getValor)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
}
Os jobs agendados: CobrancaScheduler.java
Aqui é onde o Quarkus Scheduler entra. A anotação @Scheduled aceita dois formatos: intervalo fixo com every e expressão cron com cron. Qual usar? Depende. Pra verificações frequentes, every resolve. Pra tarefas que precisam rodar em horário específico, cron é o caminho.
package dev.dellamas.fintech.scheduler;
import dev.dellamas.fintech.service.CobrancaService;
import io.quarkus.scheduler.Scheduled;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.jboss.logging.Logger;
@ApplicationScoped
public class CobrancaScheduler {
private static final Logger LOG = Logger.getLogger(CobrancaScheduler.class);
@Inject
CobrancaService cobrancaService;
@Scheduled(every = "30s")
void verificarVencimentos() {
int total = cobrancaService.marcarVencidasComoVencidas();
if (total > 0) {
LOG.infof("Vencimentos processados: %d cobrança(s) marcadas como vencidas", total);
}
}
@Scheduled(cron = "0 0 8 * * ?")
void processarCobrancasDiarias() {
int total = cobrancaService.processarPendentes();
LOG.infof("Processamento diário iniciado: %d cobrança(s) em processamento", total);
}
@Scheduled(cron = "0 0 18 * * ?")
void gerarRelatorioDiario() {
var vencidas = cobrancaService.listarVencidas().size();
var totalVencido = cobrancaService.totalVencido();
LOG.infof("Relatório diário — Cobranças vencidas: %d | Total em aberto: R$ %s",
vencidas, totalVencido);
}
}
O formato de cron padrão no Quarkus é o do Quartz (6 campos, com segundos). Se você prefere o formato Unix de 5 campos, troca no application.properties:
quarkus.scheduler.cron-type=unix
O endpoint REST: CobrancaResource.java
Pra fechar, precisamos expor o estado das cobranças. Afinal, de que adianta processar tudo em background se ninguém consegue consultar?
package dev.dellamas.fintech.resource;
import dev.dellamas.fintech.model.Cobranca;
import dev.dellamas.fintech.service.CobrancaService;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import java.util.List;
@Path("/cobrancas")
@Produces(MediaType.APPLICATION_JSON)
public class CobrancaResource {
@Inject
CobrancaService cobrancaService;
@GET
public List<Cobranca> listarTodas() {
return cobrancaService.listarTodas();
}
@GET
@Path("/vencidas")
public List<Cobranca> listarVencidas() {
return cobrancaService.listarVencidas();
}
@GET
@Path("/pendentes")
public List<Cobranca> listarPendentes() {
return cobrancaService.listarPendentes();
}
@GET
@Path("/resumo")
@Produces(MediaType.TEXT_PLAIN)
public String resumo() {
return String.format("Total vencido: R$ %s | Vencidas: %d | Pendentes: %d",
cobrancaService.totalVencido(),
cobrancaService.listarVencidas().size(),
cobrancaService.listarPendentes().size());
}
}
Configuração
application.properties:
quarkus.application.name=quarkus-scheduler-fintech
quarkus.scheduler.cron-type=quartz
Rodando
git clone https://github.com/dellamas/quarkus-scheduler-fintech
cd quarkus-scheduler-fintech
./mvnw quarkus:dev
Assim que subir, o job de verificação de vencimentos já começa a rodar a cada 30 segundos. Acompanhe no terminal e, em paralelo, teste:
curl http://localhost:8080/cobrancas
curl http://localhost:8080/cobrancas/vencidas
curl http://localhost:8080/cobrancas/resumo
Por que o Scheduler e não o Quartz?
O quarkus-scheduler é a opção leve — resolve bem pra aplicações de instância única. Precisa de clustering, com múltiplas instâncias disputando o mesmo job? Aí o quarkus-quartz faz mais sentido. Mas pra maioria dos cenários do dia a dia, o Scheduler padrão dá conta.
O código completo está no GitHub. Se fez sentido pra você, deixa uma estrela no repositório — ajuda mais do que parece.
Eu sou o Luis De Llamas, Developer Advocate na act digital, Oracle ACE e IBM Champion. Se quiser acompanhar mais conteúdo sobre Quarkus, Java e o que rola no ecossistema, me encontra aqui:
Nos vemos no próximo.
Top comments (0)