Idempotência em ingestão de pedidos no Spring Boot: o que separa o protótipo da produção
Integrar dois sistemas via pedidos parece a tarefa mais banal do mundo: recebe um JSON, salva no banco, devolve 201. Você faz isso antes do…
Idempotência em ingestão de pedidos no Spring Boot: o que separa o protótipo da produção

Integrar dois sistemas via pedidos parece a tarefa mais banal do mundo: recebe um JSON, salva no banco, devolve 201. Você faz isso antes do café.
O problema nunca é esse. O problema aparece três semanas depois, em produção, quando o sistema que te envia os pedidos sofre um timeout, reenvia a mesma requisição, e você descobre que o pedido A-123456 agora existe duas vezes no seu banco - com cobrança dobrada, estoque furado e um cliente irritado.
Este artigo é sobre como receber pedidos de forma idempotente, mesmo sob concorrência, em Java + Spring Boot. É a diferença entre um endpoint que funciona na demo e um que aguenta produção.
O cenário
Um Sistema A envia pedidos para o seu serviço. Cada pedido tem um identificador de origem — vou chamar de externalOrderId. Seu serviço precisa:
- Receber o pedido (
POST /orders). - Calcular os totais.
- Persistir.
- Disponibilizar para consulta de um Sistema B.
A regra de ouro: o mesmo externalOrderId nunca pode virar dois pedidos, não importa quantas vezes o Sistema A reenvie, nem quantas instâncias do seu serviço estejam rodando.
A tentativa ingênua (que falha)
A primeira versão que todo mundo escreve é mais ou menos esta:
Parece correto. E funciona — até duas requisições com o mesmo externalOrderId chegarem ao mesmo tempo (duas instâncias, ou duas threads). As duas executam o findBy..., as duas não encontram nada, as duas chamam save. Resultado: pedido duplicado.
Esse é um race condition clássico. O check-then-act (verifico e depois ajo) não é atômico, então a verificação não vale nada sob concorrência.
A solução correta: deixe o banco garantir a unicidade
A regra de “um por externalOrderId" não deve viver só no código - ela tem que ser uma constraint do banco:
Com isso, mesmo que duas transações tentem inserir o mesmo pedido simultaneamente, o banco rejeita a segunda. Aí o seu código só precisa tratar essa rejeição como “ah, já existe” — e devolver o pedido existente:
Repare na semântica de status:
- 201 Created quando você criou o pedido.
- 200 OK quando o pedido já existia (a requisição é idempotente — repetir não muda nada).
Essa distinção é importante: o Sistema A pode reenviar à vontade que o comportamento é sempre seguro e previsível. É exatamente o que se espera de uma API resiliente.
Totais: cuidado com double
Outro detalhe que parece bobo e quebra em produção: dinheiro não é double. 0.1 + 0.2 não dá 0.3 em ponto flutuante. Para totais de pedido, use BigDecimal com escala e arredondamento explícitos:
Escala 2, HALF_UP, sempre. Assim o total fecha igual ao da planilha do financeiro - e você não vira o dev do "centavo sumido".
Observabilidade: o que salva você às 3 da manhã
Quando algo der errado (e vai), você precisa rastrear uma requisição do começo ao fim. Um correlation id propagado por header e logado em tudo resolve 80% das investigações:
Some a isso o Actuator (/actuator/health) e logs estruturados, e você consegue operar com confiança em vez de no escuro.
Recapitulando
Uma ingestão de pedidos pronta para produção precisa de:
- Unicidade no banco (constraint), não só no código.
- Tratar a violação como idempotência (201 ao criar, 200 quando já existe).
**BigDecimal** com escala e arredondamento explícitos para dinheiro.- Correlation id + observabilidade para conseguir investigar produção.
Nenhuma dessas peças é difícil isoladamente. O trabalho está em juntar tudo, testar sob concorrência e documentar as decisões para o time não desfazer depois.
Quer pular essa parte?
Eu empacotei exatamente esse padrão num template de produção em Java 21 + Spring Boot 3: ingestão idempotente sob concorrência, cálculo correto de totais, PostgreSQL + Liquibase, observabilidade, ADRs e runbook — sobe com um docker compose up e já vem com scripts que simulam os sistemas externos.
Em vez de gastar semanas montando a fundação, você parte dela.
👉 **Conheça o Order Service Template**
Felipe Ricarte Magalhães — Arquiteto de Software (hands-on), +17 anos em backend e integrações corporativas.
Originally published at https://dev.to on June 17, 2026.
메타데이터
- post_id
- edc0293cc41e
- slug
- idempotência-em-ingestão-de-pedidos-no-spring-boot-o-que-separa-o-protótipo-da-produção-edc0293cc41e
- url
- https://medium.com/@felipericartem/idempot%C3%AAncia-em-ingest%C3%A3o-de-pedidos-no-spring-boot-o-que-separa-o-prot%C3%B3tipo-da-produ%C3%A7%C3%A3o-edc0293cc41e
- canonical_url
- https://medium.com/@felipericartem/idempot%C3%AAncia-em-ingest%C3%A3o-de-pedidos-no-spring-boot-o-que-separa-o-prot%C3%B3tipo-da-produ%C3%A7%C3%A3o-edc0293cc41e
- author_url
- https://medium.com/@felipericartem
- status
- ok
- fetched_at
- 2026-06-18 07:02:39