<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Felipe Ricarte</title>
    <description>The latest articles on DEV Community by Felipe Ricarte (@felipe_ricartem).</description>
    <link>https://dev.to/felipe_ricartem</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3989137%2F70e2edee-8421-475d-8158-4485ac4e6616.jpg</url>
      <title>DEV Community: Felipe Ricarte</title>
      <link>https://dev.to/felipe_ricartem</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/felipe_ricartem"/>
    <language>en</language>
    <item>
      <title>Multitenancy de verdade num SaaS de PDV: as decisões que você não pode errar</title>
      <dc:creator>Felipe Ricarte</dc:creator>
      <pubDate>Wed, 17 Jun 2026 16:46:01 +0000</pubDate>
      <link>https://dev.to/felipe_ricartem/multitenancy-de-verdade-num-saas-de-pdv-as-decisoes-que-voce-nao-pode-errar-5flc</link>
      <guid>https://dev.to/felipe_ricartem/multitenancy-de-verdade-num-saas-de-pdv-as-decisoes-que-voce-nao-pode-errar-5flc</guid>
      <description>&lt;p&gt;Quem decide construir um SaaS para o varejo costuma subestimar a mesma coisa: não é a tela do PDV que dá trabalho. É tudo o que sustenta vários comércios rodando no mesmo sistema sem um ver os dados do outro, sem uma cobrança vazar, sem uma nota fiscal travar a venda.&lt;/p&gt;

&lt;p&gt;Essas decisões de fundação, se erradas no início, custam uma reescrita lá na frente. Vamos às que mais importam.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Isolamento de tenant: a decisão que atravessa tudo
&lt;/h2&gt;

&lt;p&gt;Há três caminhos para multitenancy: um banco por cliente, um schema por cliente, ou uma coluna &lt;code&gt;tenant_id&lt;/code&gt; compartilhada. Para um SaaS de PMEs com muitos clientes pequenos, &lt;strong&gt;coluna compartilhada&lt;/strong&gt; é o equilíbrio certo entre custo e simplicidade — mas só funciona se o isolamento for &lt;strong&gt;inescapável&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;O erro clássico é confiar que cada query vai lembrar de filtrar por tenant. Uma esquecida e um comércio vê a venda do outro. A defesa é tornar o tenant parte do &lt;strong&gt;contexto da requisição&lt;/strong&gt;, resolvido num filtro HTTP antes de qualquer lógica:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Filtro resolve o tenant uma vez; o resto do código nunca "esquece"&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;tenantId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"X-Tenant-Id"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ou claim tenant_id do JWT em produção&lt;/span&gt;
&lt;span class="nc"&gt;TenantContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;set&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tenantId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
 {data-source-line="832"}&lt;/p&gt;

&lt;p&gt;A partir daí, todo acesso a dados parte do contexto — não de um parâmetro que alguém pode omitir.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Fiscal não pode derrubar a venda
&lt;/h2&gt;

&lt;p&gt;Emissão de NFC-e/NF-e depende da SEFAZ, e a SEFAZ cai. Se a emissão for &lt;strong&gt;síncrona&lt;/strong&gt; dentro da venda, o dia em que a SEFAZ ficar lenta, seu PDV trava e o caixa para.&lt;/p&gt;

&lt;p&gt;A venda e a emissão fiscal são responsabilidades diferentes. Use o &lt;strong&gt;padrão outbox&lt;/strong&gt;: registre a venda e a intenção de emitir na mesma transação; um processo separado emite contra o provedor fiscal com retry.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;BEGIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;venda&lt;/span&gt; &lt;span class="p"&gt;(...)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(...);&lt;/span&gt;
  &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;outbox&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tipo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'EmitirNFCe'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'PENDENTE'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;COMMIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
 {data-source-line="847"}&lt;/p&gt;

&lt;p&gt;A venda fecha na hora; a nota é emitida de forma resiliente. SEFAZ instável vira "nota pendente", não "loja parada".&lt;/p&gt;
&lt;h2&gt;
  
  
  3. Cobrar a mensalidade — e cortar o inadimplente — sem trabalho manual
&lt;/h2&gt;

&lt;p&gt;Um SaaS vive da recorrência. Mas controlar quem pagou, quem atrasou e quem deve perder acesso vira um pesadelo manual rápido. A resposta é ligar o estado da assinatura ao próprio acesso da API: o tenant inadimplente recebe &lt;strong&gt;HTTP 402 (Payment Required)&lt;/strong&gt; até regularizar.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assinatura&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;inadimplente&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PaymentRequiredException&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// 402: bloqueia o tenant, reativa sozinho ao pagar&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
 {data-source-line="859"}&lt;/p&gt;

&lt;p&gt;Suspensão e reativação automáticas, sem ninguém ligando ou desligando cliente na mão.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Onboarding self-service: o cliente entra sozinho
&lt;/h2&gt;

&lt;p&gt;Se cada novo comércio exige você criar tenant, loja e admin manualmente, o SaaS não escala. O cadastro precisa ser &lt;strong&gt;uma chamada&lt;/strong&gt; que provisiona tudo — tenant, loja, usuário admin, assinatura (com trial) e até dados de demonstração para o cliente ver valor no primeiro minuto.&lt;/p&gt;

&lt;p&gt;POST /api/v1/public/onboarding&lt;br&gt;
→ cria tenant + loja + admin + assinatura (trial) + dados demo&lt;/p&gt;

&lt;p&gt;Onboarding automático é o que separa um sistema que você "instala para cada cliente" de um SaaS que cresce sozinho.&lt;/p&gt;

&lt;h2&gt;
  
  
  O custo real é a soma
&lt;/h2&gt;

&lt;p&gt;Nenhuma dessas decisões é exótica. O problema é que são &lt;strong&gt;meses&lt;/strong&gt; somando isolamento multitenant, autenticação com RBAC, fiscal resiliente, billing recorrente, onboarding, observabilidade — antes de vender a primeira licença. E qualquer uma mal resolvida no começo vira dívida cara.&lt;/p&gt;




&lt;h3&gt;
  
  
  Atalho
&lt;/h3&gt;

&lt;p&gt;Eu construí exatamente essa fundação como um boilerplate completo: &lt;strong&gt;PDV + retaguarda multitenant&lt;/strong&gt; em Java 21/Quarkus e Next.js, com fiscal (outbox), cobrança recorrente com suspensão automática, onboarding self-service e observabilidade — tudo documentado e rodando via Docker Compose.&lt;/p&gt;

&lt;p&gt;É o &lt;strong&gt;ComercialCloud&lt;/strong&gt;. Se você vai entrar no mercado de PDV/varejo, ele te poupa meses e te entrega uma base que já nasceu pensando nos erros caros.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;[Conheça o ComercialCloud(&lt;a href="https://pay.kiwify.com.br/XOuVLZc" rel="noopener noreferrer"&gt;https://pay.kiwify.com.br/XOuVLZc&lt;/a&gt;)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Quer trocar ideia sobre alguma dessas decisões de arquitetura? Comenta aí.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>saas</category>
      <category>architecture</category>
      <category>backend</category>
    </item>
    <item>
      <title>RAG sobre o seu próprio código: o encanamento que separa o protótipo do produto</title>
      <dc:creator>Felipe Ricarte</dc:creator>
      <pubDate>Wed, 17 Jun 2026 16:42:37 +0000</pubDate>
      <link>https://dev.to/felipe_ricartem/rag-sobre-o-seu-proprio-codigo-o-encanamento-que-separa-o-prototipo-do-produto-14kf</link>
      <guid>https://dev.to/felipe_ricartem/rag-sobre-o-seu-proprio-codigo-o-encanamento-que-separa-o-prototipo-do-produto-14kf</guid>
      <description>&lt;p&gt;"Vamos colocar uma IA pra responder perguntas sobre o nosso código." A frase parece simples, e um protótipo sai numa tarde: joga uns arquivos num embedding, guarda num índice, faz a busca. Funciona na demo.&lt;/p&gt;

&lt;p&gt;O problema aparece quando isso vira produto: respostas inconsistentes, vetores que não batem com o banco, cada cliente vendo dados do outro, e um acoplamento total a um provedor de LLM. O que separa o protótipo do produto é o encanamento — e ele é mais chato do que parece.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. A dimensão do vetor é um contrato (e quase ninguém trata)
&lt;/h2&gt;

&lt;p&gt;O erro de RAG mais silencioso: você indexa com um modelo de embeddings de 1536 dimensões, depois troca para um de 768, e o banco continua esperando &lt;code&gt;vector(1536)&lt;/code&gt;. Nada explode na cara — as buscas só ficam &lt;em&gt;sutilmente erradas&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;A coluna do banco e o modelo são um &lt;strong&gt;contrato&lt;/strong&gt;. Trate como tal: valide no arranque.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Probe de arranque: materializa um embedding e falha cedo se a dimensão divergir {#probe-de-arranque-materializa-um-embedding-e-falha-cedo-se-a-dimensão-divergir  data-source-line="528"}
&lt;/span&gt;&lt;span class="n"&gt;vetor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;embedding_provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;embed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amostra&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vetor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;EMBEDDING_DIMENSION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Dimensão do modelo != vector(N) no banco&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
 {data-source-line="531"}&lt;/p&gt;

&lt;p&gt;Falhar no boot é infinitamente melhor que descobrir semanas depois que a busca semântica está degradada.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. O provedor de LLM não pode estar grudado no domínio
&lt;/h2&gt;

&lt;p&gt;No protótipo, a chamada à OpenAI fica no meio da regra. Aí você quer rodar local para testar sem custo, ou trocar para um modelo self-hosted, e percebe que o provedor está espalhado por toda parte.&lt;/p&gt;

&lt;p&gt;A saída é uma &lt;strong&gt;porta&lt;/strong&gt; (hexagonal): o domínio fala com uma interface; os adaptadores implementam cada provedor.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;LlmGateway&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;complete&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// local (determinístico, sem custo) | openai | ollama — escolhidos por configuração&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
 {data-source-line="546"}&lt;/p&gt;

&lt;p&gt;Com isso, &lt;code&gt;provider=local&lt;/code&gt; roda em dev sem chave nem rede, e produção troca para &lt;code&gt;openai&lt;/code&gt; ou &lt;code&gt;ollama&lt;/code&gt; &lt;strong&gt;sem tocar no núcleo&lt;/strong&gt;. Testes ficam determinísticos; custo de dev vai a zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Multitenancy desde o primeiro embedding
&lt;/h2&gt;

&lt;p&gt;Se a ferramenta vai servir mais de um time ou cliente, o isolamento precisa existir desde a ingestão — não dá para "adicionar depois". Cada chunk, cada vetor, cada consulta carrega o &lt;code&gt;tenant_id&lt;/code&gt;. Uma busca vetorial sem filtro de tenant é um vazamento de dados esperando para acontecer.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. RAG sozinho não basta: combine com análise estática
&lt;/h2&gt;

&lt;p&gt;Embeddings respondem "o que o código diz". Mas um diagnóstico de arquitetura útil também precisa de fatos &lt;strong&gt;determinísticos&lt;/strong&gt;: este endpoint do OpenAPI não tem schema de erro, esta migration não tem rollback, este Dockerfile roda como root, este pipeline não trava em vulnerabilidade.&lt;/p&gt;

&lt;p&gt;Isso é análise estática de artefatos — código, OpenAPI, migrations, Docker, CI — e ela dá ao relatório &lt;strong&gt;evidências rastreáveis&lt;/strong&gt;, não só texto plausível de LLM. A combinação (RAG contextual + análise factual) é o que torna o diagnóstico confiável.&lt;/p&gt;

&lt;h2&gt;
  
  
  O encanamento é o produto
&lt;/h2&gt;

&lt;p&gt;Chunking, embeddings, pgvector, probe de dimensão, porta de LLM, multitenancy, analisadores de artefatos, geração de ADR com evidência — nada disso é a "feature de IA" que aparece na demo, mas é tudo que faz ela virar produto. São semanas de fundação antes da primeira resposta confiável.&lt;/p&gt;




&lt;h3&gt;
  
  
  Atalho
&lt;/h3&gt;

&lt;p&gt;Empacotei essa fundação inteira como um boilerplate: backend &lt;strong&gt;Java 21/Quarkus&lt;/strong&gt; + worker &lt;strong&gt;Python/FastAPI&lt;/strong&gt;, com pipeline de RAG (chunking, embeddings, pgvector com probe de dimensão), análise estática de artefatos, geração de ADRs com evidências, troca de LLM por configuração (local/openai/ollama) e multitenancy — rodando via Docker Compose.&lt;/p&gt;

&lt;p&gt;É o &lt;strong&gt;ArchLens&lt;/strong&gt;. Se você vai construir uma ferramenta de IA sobre código, ele te entrega o encanamento pronto e uma arquitetura limpa para estender.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://pay.kiwify.com.br/f5tO7D7" rel="noopener noreferrer"&gt;Conheça o ArchLens&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Já apanhou de algum desses pontos montando RAG? Conta nos comentários.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>rag</category>
      <category>java</category>
      <category>python</category>
    </item>
    <item>
      <title>Cobrança recorrente em produção: o que ninguém te conta antes da primeira cobrança duplicada</title>
      <dc:creator>Felipe Ricarte</dc:creator>
      <pubDate>Wed, 17 Jun 2026 16:33:22 +0000</pubDate>
      <link>https://dev.to/felipe_ricartem/cobranca-recorrente-em-producao-o-que-ninguem-te-conta-antes-da-primeira-cobranca-duplicada-5dnp</link>
      <guid>https://dev.to/felipe_ricartem/cobranca-recorrente-em-producao-o-que-ninguem-te-conta-antes-da-primeira-cobranca-duplicada-5dnp</guid>
      <description>&lt;p&gt;Implementar cobrança recorrente parece um problema resolvido. Você integra um gateway, agenda um job pra rodar todo dia e cobra quem vence hoje. Em uma tarde está "funcionando".&lt;/p&gt;

&lt;p&gt;Aí o sistema vai pra produção, você escala pra duas instâncias, e numa madrugada o mesmo cliente é cobrado &lt;strong&gt;duas vezes&lt;/strong&gt;. Ou pior: o pagamento confirma, mas o módulo que libera o acesso nunca fica sabendo — e o cliente paga sem receber.&lt;/p&gt;

&lt;p&gt;A cobrança em si é a parte fácil. O que separa um protótipo de um billing de produção são quatro detalhes que quase ninguém trata no começo.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Concorrência: o job que roda duas vezes
&lt;/h2&gt;

&lt;p&gt;O cenário clássico: um &lt;code&gt;@Scheduled&lt;/code&gt; que busca assinaturas vencidas e cobra cada uma. Com &lt;strong&gt;uma&lt;/strong&gt; instância, funciona. Com duas (deploy sem downtime, autoscaling), as duas leem a mesma assinatura no mesmo segundo e cobram em dobro.&lt;/p&gt;

&lt;p&gt;A solução não é "garantir uma instância" — é tornar a leitura segura sob concorrência. No PostgreSQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;assinatura&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;proxima_cobranca&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'ATIVA'&lt;/span&gt;
&lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;SKIP&lt;/span&gt; &lt;span class="n"&gt;LOCKED&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
 {data-source-line="212"}&lt;/p&gt;

&lt;p&gt;&lt;code&gt;FOR UPDATE SKIP LOCKED&lt;/code&gt; faz cada instância travar e processar um lote &lt;strong&gt;disjunto&lt;/strong&gt;: ninguém espera, ninguém duplica. Escala horizontalmente sem coordenação externa.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Retry determinístico: falhar não pode ser improviso
&lt;/h2&gt;

&lt;p&gt;Cartão recusado acontece o tempo todo. A pergunta é: quantas vezes tentar, com qual intervalo, e quando desistir? Se isso for "ad hoc", você terá clientes suspensos cedo demais e outros cobrados pra sempre.&lt;/p&gt;

&lt;p&gt;Trate a falha como uma máquina de estados explícita:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1ª falha → reagenda +15 min&lt;/li&gt;
&lt;li&gt;2ª falha → +60 min&lt;/li&gt;
&lt;li&gt;3ª falha → &lt;strong&gt;suspende&lt;/strong&gt; a assinatura e desliga a renovação&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cada tentativa fica registrada e auditável. O comportamento é previsível — para você e para o cliente.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. Outbox: o evento que não pode se perder
&lt;/h2&gt;

&lt;p&gt;O erro mais caro: cobrar com sucesso e, na sequência, publicar o evento &lt;code&gt;PagamentoConfirmado&lt;/code&gt; num broker. Se a aplicação morre entre as duas operações, o pagamento aconteceu mas o resto do sistema nunca soube. Dinheiro entrou, acesso não foi liberado.&lt;/p&gt;

&lt;p&gt;O &lt;strong&gt;padrão Outbox&lt;/strong&gt; resolve: grave o evento na &lt;strong&gt;mesma transação&lt;/strong&gt; da cobrança, numa tabela &lt;code&gt;outbox&lt;/code&gt;. Um publicador lê essa tabela e entrega ao broker com retry/backoff, marcando como &lt;code&gt;DEAD&lt;/code&gt; ao estourar o limite.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;BEGIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;assinatura&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'ATIVA'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;proxima_cobranca&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;outbox&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tipo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'PagamentoConfirmado'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'PENDENTE'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;COMMIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
 {data-source-line="239"}&lt;/p&gt;

&lt;p&gt;Ou a cobrança &lt;strong&gt;e&lt;/strong&gt; o evento são persistidos, ou nenhum dos dois. Sem estado inconsistente.&lt;/p&gt;
&lt;h2&gt;
  
  
  4. Idempotência no consumidor
&lt;/h2&gt;

&lt;p&gt;O outbox garante entrega &lt;strong&gt;pelo menos uma vez&lt;/strong&gt; — então o mesmo evento pode chegar duas vezes. O consumidor precisa ser idempotente. A forma mais simples e robusta é uma constraint única na chave do evento:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;evento_processado&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;uq_evento&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;evento_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
 {data-source-line="249"}&lt;/p&gt;

&lt;p&gt;Processou de novo? A inserção viola a constraint e você ignora com segurança. Sem efeito colateral duplicado.&lt;/p&gt;

&lt;h2&gt;
  
  
  O encanamento toma mais tempo que o produto
&lt;/h2&gt;

&lt;p&gt;Nada disso é "difícil" isoladamente. O problema é que são &lt;strong&gt;semanas&lt;/strong&gt; de encanamento — concorrência, retry, outbox, idempotência, métricas, testes com containers — antes de escrever uma linha da regra de negócio que importa pro seu SaaS. E é justamente a parte onde um bug custa cliente e reputação.&lt;/p&gt;




&lt;h3&gt;
  
  
  Atalho
&lt;/h3&gt;

&lt;p&gt;Empacotei exatamente essa camada num starter kit em &lt;strong&gt;Java 21 + Spring Boot 3&lt;/strong&gt;: renovação automática, retry determinístico, outbox com idempotência, suspensão por inadimplência, métricas Prometheus e testes com Testcontainers — tudo documentado e rodando via Docker Compose.&lt;/p&gt;

&lt;p&gt;É o &lt;strong&gt;AssinaFlow&lt;/strong&gt;. Se você vai construir cobrança recorrente, ele economiza as semanas chatas e te dá uma base que já nasce pronta pra produção.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://pay.kiwify.com.br/QJUkXsO" rel="noopener noreferrer"&gt;Conheça o AssinaFlow&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Dúvidas sobre alguma das decisões acima? Comenta aí — respondo todas.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>springboot</category>
      <category>backend</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Idempotência em ingestão de pedidos no Spring Boot: o que separa o protótipo da produção</title>
      <dc:creator>Felipe Ricarte</dc:creator>
      <pubDate>Wed, 17 Jun 2026 13:10:56 +0000</pubDate>
      <link>https://dev.to/felipe_ricartem/idempotencia-em-ingestao-de-pedidos-no-spring-boot-o-que-separa-o-prototipo-da-producao-49gd</link>
      <guid>https://dev.to/felipe_ricartem/idempotencia-em-ingestao-de-pedidos-no-spring-boot-o-que-separa-o-prototipo-da-producao-49gd</guid>
      <description>&lt;p&gt;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é.&lt;/p&gt;

&lt;p&gt;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, &lt;strong&gt;reenvia a mesma requisição&lt;/strong&gt;, e você descobre que o pedido &lt;code&gt;A-123456&lt;/code&gt; agora existe &lt;strong&gt;duas vezes&lt;/strong&gt; no seu banco — com cobrança dobrada, estoque furado e um cliente irritado.&lt;/p&gt;

&lt;p&gt;Este artigo é sobre como receber pedidos de forma &lt;strong&gt;idempotente&lt;/strong&gt;, mesmo sob concorrência, em Java + Spring Boot. É a diferença entre um endpoint que funciona na demo e um que aguenta produção.&lt;/p&gt;

&lt;h2&gt;
  
  
  O cenário
&lt;/h2&gt;

&lt;p&gt;Um Sistema A envia pedidos para o seu serviço. Cada pedido tem um identificador de origem — vou chamar de &lt;code&gt;externalOrderId&lt;/code&gt;. Seu serviço precisa:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Receber o pedido (&lt;code&gt;POST /orders&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Calcular os totais.&lt;/li&gt;
&lt;li&gt;Persistir.&lt;/li&gt;
&lt;li&gt;Disponibilizar para consulta de um Sistema B.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A regra de ouro: &lt;strong&gt;o mesmo &lt;code&gt;externalOrderId&lt;/code&gt; nunca pode virar dois pedidos&lt;/strong&gt;, não importa quantas vezes o Sistema A reenvie, nem quantas instâncias do seu serviço estejam rodando.&lt;/p&gt;

&lt;h2&gt;
  
  
  A tentativa ingênua (que falha)
&lt;/h2&gt;

&lt;p&gt;A primeira versão que todo mundo escreve é mais ou menos esta:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="nf"&gt;receber&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OrderRequest&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Optional&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;existente&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findByExternalOrderId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;externalOrderId&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;existente&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isPresent&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;existente&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// já existe, retorna&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="n"&gt;novo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;calcular&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;novo&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// cria&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Parece correto. E funciona — até duas requisições com o mesmo &lt;code&gt;externalOrderId&lt;/code&gt; chegarem &lt;strong&gt;ao mesmo tempo&lt;/strong&gt; (duas instâncias, ou duas threads). As duas executam o &lt;code&gt;findBy...&lt;/code&gt;, as duas não encontram nada, as duas chamam &lt;code&gt;save&lt;/code&gt;. Resultado: &lt;strong&gt;pedido duplicado&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Esse é um &lt;em&gt;race condition&lt;/em&gt; clássico. O &lt;code&gt;check-then-act&lt;/code&gt; (verifico e depois ajo) não é atômico, então a verificação não vale nada sob concorrência.&lt;/p&gt;

&lt;h2&gt;
  
  
  A solução correta: deixe o banco garantir a unicidade
&lt;/h2&gt;

&lt;p&gt;A regra de "um por &lt;code&gt;externalOrderId&lt;/code&gt;" não deve viver só no código — ela tem que ser uma &lt;strong&gt;constraint do banco&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;uq_orders_external_id&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;external_order_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Com isso, mesmo que duas transações tentem inserir o mesmo pedido simultaneamente, &lt;strong&gt;o banco rejeita a segunda&lt;/strong&gt;. Aí o seu código só precisa tratar essa rejeição como "ah, já existe" — e devolver o pedido existente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Result&lt;/span&gt; &lt;span class="nf"&gt;receber&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OrderRequest&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="n"&gt;novo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;calcular&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;saveAndFlush&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;novo&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;          &lt;span class="c1"&gt;// flush para a violação estourar AGORA&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;criado&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;novo&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;        &lt;span class="c1"&gt;// -&amp;gt; 201 Created&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;DataIntegrityViolationException&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// outra requisição ganhou a corrida: o pedido já existe&lt;/span&gt;
        &lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="n"&gt;existente&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findByExternalOrderId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;externalOrderId&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
                              &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orElseThrow&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;jaExistia&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;existente&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// -&amp;gt; 200 OK&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repare na semântica de status:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;201 Created&lt;/strong&gt; quando &lt;em&gt;você&lt;/em&gt; criou o pedido.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;200 OK&lt;/strong&gt; quando o pedido já existia (a requisição é idempotente — repetir não muda nada).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Totais: cuidado com &lt;code&gt;double&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Outro detalhe que parece bobo e quebra em produção: &lt;strong&gt;dinheiro não é &lt;code&gt;double&lt;/code&gt;&lt;/strong&gt;. &lt;code&gt;0.1 + 0.2&lt;/code&gt; não dá &lt;code&gt;0.3&lt;/code&gt; em ponto flutuante. Para totais de pedido, use &lt;code&gt;BigDecimal&lt;/code&gt; com escala e arredondamento explícitos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;BigDecimal&lt;/span&gt; &lt;span class="n"&gt;lineTotal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;unitPrice&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;multiply&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BigDecimal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;valueOf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;quantity&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setScale&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;RoundingMode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;HALF_UP&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;BigDecimal&lt;/span&gt; &lt;span class="n"&gt;totalAmount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;Item:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;lineTotal&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;reduce&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BigDecimal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ZERO&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;BigDecimal:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setScale&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;RoundingMode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;HALF_UP&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Escala 2, &lt;code&gt;HALF_UP&lt;/code&gt;, sempre. Assim o total fecha igual ao da planilha do financeiro — e você não vira o dev do "centavo sumido".&lt;/p&gt;

&lt;h2&gt;
  
  
  Observabilidade: o que salva você às 3 da manhã
&lt;/h2&gt;

&lt;p&gt;Quando algo der errado (e vai), você precisa &lt;strong&gt;rastrear uma requisição&lt;/strong&gt; do começo ao fim. Um &lt;code&gt;correlation id&lt;/code&gt; propagado por header e logado em tudo resolve 80% das investigações:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;correlationId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Optional&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofNullable&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getHeader&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"X-Correlation-Id"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orElse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;UUID&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;randomUUID&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;span class="no"&gt;MDC&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"correlationId"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;correlationId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some a isso o Actuator (&lt;code&gt;/actuator/health&lt;/code&gt;) e logs estruturados, e você consegue operar com confiança em vez de no escuro.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recapitulando
&lt;/h2&gt;

&lt;p&gt;Uma ingestão de pedidos pronta para produção precisa de:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Unicidade no banco&lt;/strong&gt; (constraint), não só no código.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tratar a violação&lt;/strong&gt; como idempotência (201 ao criar, 200 quando já existe).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;BigDecimal&lt;/code&gt;&lt;/strong&gt; com escala e arredondamento explícitos para dinheiro.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Correlation id + observabilidade&lt;/strong&gt; para conseguir investigar produção.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Nenhuma dessas peças é difícil isoladamente. O trabalho está em juntar tudo, testar sob concorrência e &lt;strong&gt;documentar as decisões&lt;/strong&gt; para o time não desfazer depois.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quer pular essa parte?
&lt;/h2&gt;

&lt;p&gt;Eu empacotei exatamente esse padrão num &lt;strong&gt;template de produção em Java 21 + Spring Boot 3&lt;/strong&gt;: ingestão idempotente sob concorrência, cálculo correto de totais, PostgreSQL + Liquibase, observabilidade, &lt;strong&gt;ADRs e runbook&lt;/strong&gt; — sobe com um &lt;code&gt;docker compose up&lt;/code&gt; e já vem com scripts que simulam os sistemas externos.&lt;/p&gt;

&lt;p&gt;Em vez de gastar semanas montando a fundação, você parte dela.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://pay.kiwify.com.br/l2v6dYI" rel="noopener noreferrer"&gt;Conheça o Order Service Template&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Felipe Ricarte Magalhães — Arquiteto de Software (hands-on), +17 anos em backend e integrações corporativas.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>springboot</category>
      <category>architecture</category>
      <category>backend</category>
    </item>
  </channel>
</rss>
