Programação Reativa em Java com Project Reactor
Programação reativa é um paradigma de desenvolvimento focado em fluxos de dados assíncronos e propagação de mudanças. Em Java, esse conceito é implementado através da especificação Reactive Streams e sua implementação mais popular — a biblioteca Project Reactor (parte do ecossistema Spring WebFlux).
Diferentemente do código imperativo tradicional, onde o programa executa comandos sequencialmente, a abordagem reativa permite processar dados à medida que chegam, sem bloquear threads de execução. Isso é especialmente importante para sistemas de alta carga, microsserviços e aplicações com grande número de requisições simultâneas.
Componentes principais do Reactor
A biblioteca fornece dois tipos principais de sequências reativas:
- Mono<T> — representa um fluxo que pode conter 0 ou 1 elemento. Usado para operações assíncronas únicas (ex.: requisição HTTP, obtenção de registro do banco de dados).
- Flux<T> — representa um fluxo que pode conter de 0 a N elementos. Adequado para coleções, fluxos de eventos ou dados que chegam em partes.
Ambos os tipos implementam a interface Publisher da especificação Reactive Streams e suportam o mecanismo de backpressure (contrapressão) — a capacidade do assinante controlar a velocidade de recebimento dos dados.
Exemplo: criação e processamento de Flux
Vamos considerar um exemplo simples de criação de um fluxo reativo de números e seu processamento:
import reactor.core.publisher.Flux;
public class ReactorExample { public static void main(String[] args) { Flux<Integer> numbers = Flux.just(1, 2, 3, 4, 5);
numbers .map(n -> n * 2) // transformação .filter(n -> n % 3 == 0) // filtragem .subscribe( data -> System.out.println("Recebido: " + data), error -> System.err.println("Erro: " + error), () -> System.out.println("Finalizado!") ); }}Neste exemplo:
Flux.just()cria um fluxo de cinco números.map()multiplica cada elemento por 2.filter()mantém apenas os números múltiplos de 3.subscribe()inicia o processamento — exibe o resultado, trata erros e sinaliza a conclusão.
Assincronia e trabalho com Mono
O Reactor permite executar operações assíncronas sem bloquear threads. Por exemplo, simulação de uma requisição a um serviço remoto:
import reactor.core.publisher.Mono;import java.time.Duration;
public class AsyncExample { public static void main(String[] args) throws InterruptedException { Mono<String> asyncResult = Mono.fromSupplier(() -> { try { Thread.sleep(1000); // simulação de atraso } catch (InterruptedException e) { e.printStackTrace(); } return "Dados recebidos"; }).subscribeOn(Schedulers.boundedElastic()); // executa em outra thread
System.out.println("Requisição enviada..."); asyncResult.subscribe(System.out::println); Thread.sleep(2000); // aguarda a conclusão }}Ponto-chave: a chamada .subscribeOn() transfere a execução da operação custosa para uma thread separada, sem bloquear a principal. O assinante receberá o resultado de forma assíncrona.
Tratamento de erros em fluxos reativos
Diferentemente do try-catch, na programação reativa os erros são tratados através de operadores:
Flux<String> fluxWithError = Flux.just("A", "B", "C") .map(s -> { if ("B".equals(s)) t