No Spring, misturar transação com chamada HTTP é armadilha
Sua transação pode estar esperando a internet.
Esse problema aparece muito em código de produção porque a chamada externa parece pequena demais para preocupar.
"Já tenho o id, é só fazer um request HTTP para buscar o resto."
Quando isso entra dentro de um método transacional, a aplicação paga um custo técnico que quase nunca ficou explícito no desenho.
O erro parece inocente
Um exemplo comum é este:
@Transactional
public void processarPedido(Long pedidoId) {
Pedido pedido = pedidoRepository.findById(pedidoId)
.orElseThrow();
ClienteResumo cliente = clienteApi.buscarResumo(pedido.getClienteId());
pedido.atualizarSegmento(cliente.segmento());
pedidoRepository.save(pedido);
}
Aqui a anotação pode até estar funcionando corretamente.
O problema não é o proxy.
O problema é deixar a transação aberta enquanto o método espera uma resposta de rede.
Esse ponto conversa diretamente com o post anterior sobre @Transactional e proxy.
Lá o erro era acreditar que a transação existia quando a chamada nem passava pela infraestrutura do Spring.
Aqui o problema é outro:
a transação pode existir de verdade e, ainda assim, estar desenhada no lugar errado.
O que piora na prática
Enquanto o HTTP está em andamento, a transação continua viva.
Isso significa:
- lock aberto por mais tempo;
- conexão ocupada por mais tempo;
- mais latência para concluir o fluxo;
- mais chance de contenção e timeout quando o volume cresce.
Se o serviço remoto oscila, fica lento ou começa a falhar, sua transação local degrada junto.
Uma decisão aparentemente pequena espalha custo para banco, pool de conexão e concorrência da aplicação inteira.
Mesmo enriquecimento pode virar gargalo
O ponto importante aqui é que nem sempre existe efeito colateral externo.
Muitas vezes é só enriquecimento.
Você consulta outro serviço para buscar nome, segmento, score, limite, endereço, qualquer dado complementar.
Como parece "só leitura", o código passa uma sensação de inocência.
Mas a transação continua aberta esperando rede do mesmo jeito.
Ou seja: não precisa haver rollback envolvido para isso já ser um mau desenho transacional.
E quando existe efeito externo, fica pior
Se a chamada HTTP faz algo fora do seu banco, o problema sobe de nível.
Rollback local não desfaz efeito que já aconteceu em outro sistema.
Se o serviço externo confirmou uma ação e, depois disso, a transação local falhou, você terminou o fluxo com dois lados em estados diferentes.
Transação de banco não vira coordenação distribuída só porque existe um @Transactional cercando o método.
O desenho melhor
Se o dado remoto é necessário para decidir o fluxo, em geral ele deveria ser obtido antes da transação ou em uma fronteira arquitetural clara, e não no meio dela como um detalhe inocente.
Um desenho mais honesto costuma separar a coordenação externa da parte realmente transacional:
@Service
public class ProcessamentoPedidoUseCase {
private final ClienteApi clienteApi;
private final PedidoService pedidoService;
public ProcessamentoPedidoUseCase(ClienteApi clienteApi, PedidoService pedidoService) {
this.clienteApi = clienteApi;
this.pedidoService = pedidoService;
}
public void executar(Long pedidoId) {
ClienteResumo cliente = clienteApi.buscarResumoPorPedido(pedidoId);
pedidoService.processarComDadosDoCliente(pedidoId, cliente);
}
}
@Service
public class PedidoService {
private final PedidoRepository pedidoRepository;
public PedidoService(PedidoRepository pedidoRepository) {
this.pedidoRepository = pedidoRepository;
}
@Transactional
public void processarComDadosDoCliente(Long pedidoId, ClienteResumo cliente) {
Pedido pedido = pedidoRepository.findById(pedidoId)
.orElseThrow();
pedido.atualizarSegmento(cliente.segmento());
}
}
Aqui a espera de rede ficou fora da transação.
O trecho que realmente precisa de consistência local ficou menor, mais previsível e mais barato para o banco.
Se existe efeito externo, a modelagem fica ainda mais séria: compensação, orquestração, outbox, evento, retry e idempotência entram na conversa.
Em outras palavras:
uma coisa é consultar outro sistema antes de abrir a transação local;
outra bem diferente é tentar usar a transação do banco como se ela controlasse tempo de rede e efeitos distribuídos.
O ponto aqui não é "nunca chame HTTP".
O ponto é: não esconda espera de rede dentro de uma transação local como se fosse um detalhe de implementação.
O ponto que vale fixar
Transação de banco não protege efeitos fora dele.
E, mesmo quando a chamada HTTP é só enriquecimento, deixar a transação esperando a internet continua sendo uma armadilha.
