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.