Comunicação Baseada em Eventos no Spring Boot: Desacoplamento na Prática
No nosso artigo anterior sobre Spring Modulith, tocamos em um ponto crucial para evitar o temido "código espaguete": a Comunicação Baseada em Eventos. Mas você sabia que não precisa de um Kafka ou RabbitMQ para começar a trabalhar orientado a eventos? Muitos desenvolvedores acham que eventos são sinônimo de microsserviços e mensageria distribuída. A verdade é que o próprio Spring Boot possui um mecanismo nativo e poderoso para publicação e escuta de eventos na mesma JVM.
1. O Básico: Publicando e Escutando Eventos
A premissa é simples: quando algo importante acontece no seu sistema (ex: "Pedido Criado"), em vez de chamar diretamente a classe que emite a nota fiscal e a que envia o email, você simplesmente fala para o sistema que o evento ocorreu. Quem estiver interessado, que reaja.
O Evento (Usando Java Records):
package com.alexsousadev.order;
public record OrderCreatedEvent(String orderId, String customerEmail) {}
O Publicador:
package com.alexsousadev.order;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
private final ApplicationEventPublisher publisher;
public OrderService(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public void createOrder(String orderId, String email) {
// Lógica de negócio e persistência...
System.out.println("Pedido salvo no banco: " + orderId);
// Publica o evento
publisher.publishEvent(new OrderCreatedEvent(orderId, email));
}
}
O Ouvinte (Listener):
package com.alexsousadev.notification;
import com.alexsousadev.order.OrderCreatedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class EmailNotificationListener {
@EventListener
public void handleOrderCreated(OrderCreatedEvent event) {
System.out.println("Enviando email de confirmação para: " + event.customerEmail());
}
}
2. A Armadilha das Transações (Erros Comuns)
Dica / O Problema do Mundo Real: Usar o
@EventListenersimples tem um perigo enorme. Se o seuOrderServicesalvar o pedido, disparar o evento e, logo em seguida, der um erro (ex: falha de constraint no banco de dados), a transação fará o rollback do pedido. O problema? O ouvinte já enviou o email! O cliente receberá a confirmação de um pedido que não existe.
A solução no Spring Boot é substituir o @EventListener pelo @TransactionalEventListener. Ele vincula a execução do evento à transação do banco de dados do publicador.
import org.springframework.transaction.event.TransactionPhase;
import org.springframework.transaction.event.TransactionalEventListener;
@Component
public class EmailNotificationListener {
// O evento só será processado se (e somente se) o commit no banco der sucesso
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleOrderCreated(OrderCreatedEvent event) {
System.out.println("Enviando email com segurança para: " + event.customerEmail());
}
}
3. Assincronicidade: Liberando a Thread Principal
Por padrão, os ouvintes de eventos no Spring são síncronos. Ou seja, o usuário que clicou em "Comprar" vai ficar esperando a thread principal terminar de salvar no banco, depois ir lá e enviar o email, para só então receber a resposta 200 OK.
Para resolver isso e deixar a resposta rápida, precisamos rodar o listener em outra Thread.
Passo a passo:
Adicione
@EnableAsyncna classe principal da sua aplicação (ou em uma classe de configuração).Anote seu método do Listener com
@Async:
import org.springframework.scheduling.annotation.Async;
import org.springframework.transaction.event.TransactionalEventListener;
@Component
public class EmailNotificationListener {
@Async
@TransactionalEventListener
public void handleOrderCreatedAsync(OrderCreatedEvent event) {
// Este código agora roda em uma thread separada em background
System.out.println("Processando envio de email em background...");
}
}
Conclusão
Implementar comunicação baseada em eventos internamente (In-VM) usando ApplicationEventPublisher e @TransactionalEventListener é o primeiro grande passo para um Monolito Modular. Você remove o acoplamento rígido (injeções de dependência desnecessárias), melhora a testabilidade e prepara seu código para, se um dia for necessário, plugar um broker de mensageria real sem ter que reescrever toda a lógica de domínio.