← Back to list

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…

Felipericartem · 2026-06-17 13:11 · 0 claps · 2.8 min read
#java #spring-boot #software-architecture #backend #programming
Open on Medium ↗
Wiki topics: STP · Startups & Venture 💻 · Programming 🌐 · Web Development 🏛️ · Architecture

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:

  1. Receber o pedido (POST /orders).
  2. Calcular os totais.
  3. Persistir.
  4. 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:

  1. Unicidade no banco (constraint), não só no código.
  2. Tratar a violação como idempotência (201 ao criar, 200 quando já existe).
  3. **BigDecimal** com escala e arredondamento explícitos para dinheiro.
  4. 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