<?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: Tarcisio Araújo </title>
    <description>The latest articles on DEV Community by Tarcisio Araújo  (@tarcisioaraujo).</description>
    <link>https://dev.to/tarcisioaraujo</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1145534%2F77ab7d93-bb72-4db4-b6d3-af3d46598d66.jpeg</url>
      <title>DEV Community: Tarcisio Araújo </title>
      <link>https://dev.to/tarcisioaraujo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tarcisioaraujo"/>
    <language>en</language>
    <item>
      <title>Pagamentos idempotentes: Um Study Case de Arquitetura com Redis e Banco de Dados</title>
      <dc:creator>Tarcisio Araújo </dc:creator>
      <pubDate>Sat, 02 May 2026 13:59:43 +0000</pubDate>
      <link>https://dev.to/tarcisioaraujo/pagamentos-idempotentes-um-study-case-de-arquitetura-com-redis-e-banco-de-dados-3n06</link>
      <guid>https://dev.to/tarcisioaraujo/pagamentos-idempotentes-um-study-case-de-arquitetura-com-redis-e-banco-de-dados-3n06</guid>
      <description>&lt;p&gt;Recentemente, passei por um daqueles momentos em que o mesmo conceito começa a aparecer em contextos completamente diferentes e, quando isso acontece, geralmente é um sinal de que vale a pena prestar atenção.&lt;/p&gt;

&lt;p&gt;A bola da vez foi: &lt;strong&gt;idempotência&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Na aula de compiladores da faculdade: idempotência&lt;/li&gt;
&lt;li&gt;Vídeo novo do Augusto Galego sobre: idempotência&lt;/li&gt;
&lt;li&gt;Problema específico no trabalho, o que faltava? Idempotência&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mas enfim, o que significa isso? &lt;/p&gt;

&lt;p&gt;&lt;code&gt;Idempotência&lt;/code&gt; é uma propriedade da matemática e da computação que descreve operações que podem ser executadas uma ou várias vezes, mas produzem o mesmo estado final como se tivessem sido executadas apenas uma vez.&lt;/p&gt;

&lt;p&gt;Trazendo isso para o mundo &lt;em&gt;backend&lt;/em&gt;, o problema fica mais interessante quando pensamos em operações reais, com efeitos colaterais reais: criar registros, disparar &lt;em&gt;jobs&lt;/em&gt;, consumir APIs externas, processar pagamentos ou alterar estados importantes no sistema.&lt;/p&gt;

&lt;p&gt;Nesse artigo, vou contar um pouco sobre como estudei esse conceito de forma aplicada, em um &lt;strong&gt;cenário real de problema arquitetural&lt;/strong&gt;. &lt;/p&gt;




&lt;h2&gt;
  
  
  O caso📋​
&lt;/h2&gt;

&lt;p&gt;Para esse estudo, vou apresentar a evolução de um &lt;em&gt;webhook&lt;/em&gt; responsável por processar &lt;strong&gt;pagamentos&lt;/strong&gt;. Nesse cenário, consideramos que o processamento assíncrono executa &lt;strong&gt;operações custosas&lt;/strong&gt;, como chamadas para APIs externas.&lt;/p&gt;

&lt;p&gt;Por isso, quando a mesma requisição é processada mais de uma vez, seja por instabilidade de rede, &lt;em&gt;retry&lt;/em&gt; automático ou falha temporária na comunicação podemos gerar efeitos indesejados, como custo duplicado, inconsistência de dados ou até o processamento repetido de uma operação que deveria acontecer apenas uma vez.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkbm4h4x27s40tzqj02yt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkbm4h4x27s40tzqj02yt.png" alt="Ilustração de processamento de pagamento" width="800" height="198"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Como eu fiz 👷‍♂️​​
&lt;/h2&gt;

&lt;p&gt;Comecei implementando o &lt;em&gt;webhook&lt;/em&gt; puro, sem nenhuma preocupação com processamento duplicado.&lt;/p&gt;

&lt;p&gt;Com a rota funcionando, parti para a implementação de diferentes estratégias para lidar com idempotência na operação. Para isso, criei um &lt;em&gt;middleware&lt;/em&gt; responsável por interceptar a requisição antes do processamento principal.&lt;/p&gt;

&lt;p&gt;Com o tempo, percebi que esse &lt;em&gt;middleware&lt;/em&gt; estava ficando cheio de responsabilidades. Além disso, a própria solução foi atingindo diferentes níveis de maturidade conforme eu entendia melhor o problema.&lt;/p&gt;

&lt;p&gt;Foi então que decidi separar a implementação em 4 rotas diferentes, a fim de destacar cada nível: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Sem proteção&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Lock&lt;/em&gt; temporário com Redis&lt;/li&gt;
&lt;li&gt;Idempotência persistente no banco&lt;/li&gt;
&lt;li&gt;Idempotência persistente + &lt;em&gt;lock&lt;/em&gt; temporário&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Fase 1️⃣​
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Sem mecanismos para idempotência
&lt;/h3&gt;

&lt;p&gt;O problema mora aqui. &lt;/p&gt;

&lt;p&gt;Durante o estudo todo vamos considerar a mesma estrutura de &lt;em&gt;payload&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"payer_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Fulano Rico"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"payer_document"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"123456"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"amount_in_cents"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"bank_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"branch_number"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1234"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"account_number"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"56789-0"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Esse &lt;em&gt;payload&lt;/em&gt; representa que o Fulano Rico quer transferir &lt;strong&gt;R$ 100,00&lt;/strong&gt; para a conta &lt;code&gt;"56789-0"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;O problema começa quando lembramos que sistemas distribuídos falham: a rede pode oscilar, a resposta pode demorar, o serviço de origem pode entender que a requisição falhou e, por segurança, reenviar o mesmo evento.&lt;/p&gt;

&lt;p&gt;Nesse caso, sem nenhuma implementação para proteção a mesma ação seria processada duas vezes, gerando inconsistências e custos duplicados 🤯​&lt;/p&gt;
&lt;h2&gt;
  
  
  Fase 2️⃣
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Redis com hash de &lt;em&gt;payload&lt;/em&gt; normalizado
&lt;/h3&gt;

&lt;p&gt;Meu primeiro pensamento foi: “Se o mesmo &lt;em&gt;payload&lt;/em&gt; chegar mais de uma vez, vou barrar as próximas requisições e processar apenas a primeira”.&lt;/p&gt;

&lt;p&gt;Para começar, precisamos gerar uma chave que identifique a intenção daquele pagamento. Para isso, utilizei a função nativa do PHP &lt;code&gt;hash()&lt;/code&gt; para gerar um identificador a partir do &lt;em&gt;payload&lt;/em&gt; recebido.&lt;/p&gt;

&lt;p&gt;Um detalhe importante aqui é a normalização do &lt;em&gt;payload&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Por exemplo: se o campo &lt;code&gt;payer_name&lt;/code&gt; chegar primeiro na primeira requisição e por último na segunda, isso não deveria gerar uma chave diferente. Afinal, apesar da ordem dos campos ter mudado, a intenção da operação continua sendo a mesma.&lt;/p&gt;

&lt;p&gt;Por isso, antes de gerar o hash, o &lt;em&gt;payload&lt;/em&gt; precisa passar por um processo de normalização, garantindo que a mesma estrutura lógica produza sempre a mesma chave.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Para o &lt;em&gt;payload&lt;/em&gt; mencionado na &lt;strong&gt;Fase 1&lt;/strong&gt;, a chave gerada ficaria assim:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;"webhook:idempotency:cc4efc0d38d457683b2ece7072e31e55504a04853c48c5799e46921278fff18f"&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Com a chave em mãos, o Redis entra como um bloqueio rápido e temporário.&lt;/p&gt;

&lt;p&gt;Antes de processar o pagamento, a aplicação tenta salvar essa chave usando &lt;code&gt;SET NX&lt;/code&gt; com TTL. O &lt;code&gt;NX&lt;/code&gt; garante que a chave só será criada se ela ainda não existir. Na prática, isso significa que a primeira requisição segue o fluxo normalmente, enquanto as próximas, dentro da janela do TTL, são barradas como duplicadas.&lt;/p&gt;

&lt;p&gt;Já o TTL evita que essa chave fique registrada para sempre no Redis.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$idempotencyKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;idempotencyKeyGenerator&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$normalizedPayload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nc"&gt;Redis&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'EX'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'NX'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'message'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Request already processed'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;409&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;//Posteriormente é realizado um Redis::del($idempotencyKey) caso o processamento em si falhe.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Essa solução é muito eficiente em termos de performance e concorrência imediata, mas levanta alguns pontos de atenção:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;O que diferencia um &lt;em&gt;retry&lt;/em&gt; do mesmo pagamento de um novo pagamento intencional com os mesmos dados?&lt;/li&gt;
&lt;li&gt;Qual deve ser o tempo ideal de duração desse &lt;em&gt;lock&lt;/em&gt;?&lt;/li&gt;
&lt;li&gt;O que acontece se o processamento demorar mais que o TTL?&lt;/li&gt;
&lt;li&gt;O que acontece se a aplicação cair depois de criar a chave, mas antes de concluir o pagamento?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pensando mais profundamente nessa solução, percebemos uma limitação importante: não temos garantia &lt;strong&gt;real&lt;/strong&gt; sobre a intenção do evento.&lt;/p&gt;

&lt;p&gt;Ou seja, dois &lt;em&gt;payloads&lt;/em&gt; iguais podem significar a mesma tentativa de pagamento sendo reenviada ou duas tentativas legítimas de pagamento com os mesmos dados.&lt;/p&gt;
&lt;h2&gt;
  
  
  Fase ​3️⃣
&lt;/h2&gt;

&lt;p&gt;Pensando nesse problema, passei a estudar como aplicações reais fazem para diferenciar a intenção da operação.&lt;/p&gt;

&lt;p&gt;Uma abordagem comum é utilizar uma &lt;code&gt;Idempotency-Key&lt;/code&gt;, enviada pelo consumidor da API, para controlar a unicidade da operação. Esse consumidor pode ser uma máquina de cartão, um &lt;em&gt;gateway&lt;/em&gt; de pagamento ou até uma aplicação terceira.&lt;/p&gt;

&lt;p&gt;Mas só isso não pareceu suficiente, ainda temos a questão do tempo de bloqueio e a visibilidade de recebimento dos eventos.&lt;/p&gt;

&lt;p&gt;Para isso, decidi criar um objeto &lt;code&gt;PaymentWebhookReceipt&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"idempotency_key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"webhook:idempotency:{Hash da Idempotency-Key}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; 
  &lt;/span&gt;&lt;span class="nl"&gt;"payload"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"payer_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Fulano Rico"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"payer_document"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"123456"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"amount_in_cents"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"bank_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"branch_number"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1234"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"account_number"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"56789-0"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"received"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"processed_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"failed_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"failure_reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Esse objeto funciona como um recibo do evento recebido. Ele registra informações importantes, como a chave de idempotência utilizada pelo cliente, o payload original, o status do processamento e os dados relacionados ao resultado da solicitação.&lt;/p&gt;

&lt;p&gt;A chave de idempotência deve ser única no banco de dados. Dessa forma, se a mesma chave for recebida novamente, a aplicação consegue identificar que aquela operação já passou pelo sistema antes.&lt;/p&gt;

&lt;p&gt;Com a possibilidade dos seguintes &lt;em&gt;status&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;RECEIVED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'received'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;PROCESSING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'processing'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;PROCESSED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'processed'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;FAILED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'failed'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Ok, mas como ficou esse fluxo na prática?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foav7kn77u89auz8btkib.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foav7kn77u89auz8btkib.png" alt="Diagrama do fluxo de idempotência persistente no banco" width="800" height="505"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Um ponto importante é decidir o que acontece quando a operação falha.&lt;/p&gt;

&lt;p&gt;A mesma &lt;code&gt;Idempotency-Key&lt;/code&gt; pode permitir uma nova tentativa ou exigir uma nova chave, dependendo da regra de negócio.&lt;/p&gt;

&lt;p&gt;Agora a identidade da operação está explícita com &lt;code&gt;Idempotency-Key&lt;/code&gt; sendo informado pelo cliente, e com o banco de dados como fonte de verdade para saber o status do evento recebido.&lt;/p&gt;

&lt;p&gt;Agora temos uma garantia muito mais forte de evitar reprocessamento duplicado da mesma operação.&lt;/p&gt;

&lt;p&gt;Porém, toda requisição duplicada tem que chegar no banco dados para conferir a existência. Se pensarmos em um cenário com muitas requisições e muita concorrência isso pode gerar pressão desnecessária no banco de dados.&lt;/p&gt;
&lt;h2&gt;
  
  
  Fase 4️⃣
&lt;/h2&gt;

&lt;p&gt;Com a solução anterior, temos garantia forte de idempotência, porém podemos melhorar a questão da performance. &lt;/p&gt;

&lt;p&gt;Uma solução híbrida, utilizando o Redis para &lt;em&gt;lock&lt;/em&gt; rápido e temporário o banco para a rastreabilidade, persistência e fonte de verdade.&lt;/p&gt;

&lt;p&gt;Para isso, mantive toda a estrutura de estados da Fase 3, usando o &lt;code&gt;PaymentWebhookReceipt&lt;/code&gt; para registrar o ciclo de vida do evento. Também reaproveitei a ideia de &lt;em&gt;lock&lt;/em&gt; temporário da Fase 2, mas com uma diferença importante: em vez de gerar a chave a partir do &lt;em&gt;payload&lt;/em&gt;, passamos a utilizar a &lt;code&gt;Idempotency-Key&lt;/code&gt; enviada pelo cliente.&lt;/p&gt;

&lt;p&gt;Além disso, o processo de &lt;em&gt;lock&lt;/em&gt; e &lt;em&gt;release&lt;/em&gt; também foi melhorado.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Agora, em cada processamento iremos utilizar a seguinte estrutura de chave-&amp;gt;valor: &lt;/p&gt;

&lt;p&gt;&lt;code&gt;"Idempotency-Key" -&amp;gt; "UUID Gerado na execução para identificar processamento atual"&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Dessa forma, o Redis passa a guardar duas informações importantes: a existência do &lt;em&gt;lock&lt;/em&gt; e o identificador da execução que está em posse dele naquele momento.&lt;/p&gt;

&lt;p&gt;Essa mudança impacta diretamente a forma como fazemos o &lt;em&gt;release&lt;/em&gt; do &lt;em&gt;lock&lt;/em&gt;. Não basta simplesmente deletar a chave ao final do processamento, porque uma execução atrasada poderia acabar removendo um &lt;em&gt;lock&lt;/em&gt; criado por outra requisição.&lt;/p&gt;

&lt;p&gt;Por isso, antes de remover a chave, comparamos o UUID da execução atual com o valor salvo no Redis. Essa comparação seguida da remoção precisa acontecer de forma atômica, e é exatamente aqui que entra o script Lua.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'get'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;KEYS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ARGV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'del'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;KEYS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Esse script só remove a chave se o valor salvo no Redis ainda for o UUID da execução atual. Caso contrário, ele retorna &lt;code&gt;0&lt;/code&gt; e preserva o &lt;em&gt;lock&lt;/em&gt;, evitando que uma execução antiga remova o &lt;em&gt;lock&lt;/em&gt; de outra.&lt;/p&gt;

&lt;p&gt;Assim temos um serviço com as seguintes características:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Eficiente, com Redis fazendo um &lt;em&gt;lock&lt;/em&gt; rápido para concorrência em um certo espaço de tempo.&lt;/li&gt;
&lt;li&gt;Persistente, com banco de dados como fonte de verdade.&lt;/li&gt;
&lt;li&gt;Rastreável, mantendo histórico e status do processamento no &lt;code&gt;PaymentWebhookReceipt&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;Mais segura no &lt;em&gt;release&lt;/em&gt; do &lt;em&gt;lock&lt;/em&gt;, usando Lua para evitar que uma execução remova o &lt;em&gt;lock&lt;/em&gt; de outra.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Resultados!
&lt;/h2&gt;

&lt;p&gt;No fim, conseguimos trabalhar diferentes níveis de maturidade do serviço. &lt;/p&gt;

&lt;p&gt;Na fase 1 escancaramos o problema.&lt;/p&gt;

&lt;p&gt;Na Fase 2, criamos um &lt;em&gt;lock&lt;/em&gt; temporário com Redis para lidar com concorrência imediata. Mas ainda pecava em idempotência real, durabilidade e rastreabilidade.&lt;/p&gt;

&lt;p&gt;Na Fase 3, passamos a ter uma garantia mais forte e durável de idempotência, baseada em uma &lt;code&gt;Idempotency-Key&lt;/code&gt; enviada pelo cliente. Porém, toda requisição duplicada ainda precisava chegar ao banco de dados, o que poderia gerar pressão em cenários de alta concorrência.&lt;/p&gt;

&lt;p&gt;Por fim, na Fase 4, combinamos o melhor das duas abordagens anteriores: Redis para &lt;em&gt;lock&lt;/em&gt; rápido e temporário, banco de dados como fonte de verdade e Lua para liberar o &lt;em&gt;lock&lt;/em&gt; com segurança.&lt;/p&gt;

&lt;p&gt;Com isso, chegamos a uma solução mais madura, rastreável, performática e com garantia forte de idempotência.&lt;/p&gt;


&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;Esse foi meu estudo arquitetural sobre idempotência aplicado a um &lt;em&gt;webhook&lt;/em&gt; de pagamentos.&lt;/p&gt;

&lt;p&gt;A principal lição que tirei desse processo é que cada solução traz impactos positivos e negativos para a aplicação.&lt;/p&gt;

&lt;p&gt;No fim, cabe a nós, como desenvolvedores e arquitetos, entender os &lt;em&gt;trade-offs&lt;/em&gt;, adaptar a solução ao contexto e combinar estratégias para extrair benefícios e mitigar limitações.&lt;/p&gt;

&lt;p&gt;E, como quase tudo em arquitetura de software: depende do escopo, do risco e da criticidade da operação.&lt;/p&gt;

&lt;p&gt;Muito obrigado por ler até aqui! ​❤️​&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LinkedIn:&lt;/strong&gt; &lt;a href="https://www.linkedin.com/in/tarcisioaraujo7/" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/tarcisioaraujo7/&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;E-mail:&lt;/strong&gt; &lt;a href="mailto:tarcisio.olv@gmail.com"&gt;tarcisio.olv@gmail.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Também deixei o repositório com a implementação completa, testes automatizados para cada fase, ambiente Docker e pipeline de qualidade:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/TarcisioAraujo7" rel="noopener noreferrer"&gt;
        TarcisioAraujo7
      &lt;/a&gt; / &lt;a href="https://github.com/TarcisioAraujo7/idempotent-webhook-study-case" rel="noopener noreferrer"&gt;
        idempotent-webhook-study-case
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Estudo de Arquitetura: Idempotência em Webhooks de Pagamento&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/TarcisioAraujo7/idempotent-webhook-study-case/actions/workflows/ci.yml" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/TarcisioAraujo7/idempotent-webhook-study-case/actions/workflows/ci.yml/badge.svg" alt="CI"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Estudo de arquitetura backend em Laravel sobre idempotência aplicada a webhooks de pagamento.&lt;/p&gt;

&lt;p&gt;O objetivo é demonstrar, de forma prática, como diferentes estratégias lidam com o mesmo problema: uma requisição de webhook pode chegar mais de uma vez e não deve gerar efeitos duplicados.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Escopo&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;Este projeto simula um endpoint de webhook de pagamento que despacha o processamento assíncrono de uma transferência bancária.&lt;/p&gt;

&lt;p&gt;O estudo compara quatro abordagens:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sem proteção contra duplicidade;&lt;/li&gt;
&lt;li&gt;deduplicação temporária com Redis;&lt;/li&gt;
&lt;li&gt;idempotência durável com banco de dados;&lt;/li&gt;
&lt;li&gt;estratégia híbrida usando Redis e banco.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Problema Estudado&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;Webhooks podem ser reenviados por timeout, falha de rede, retry automático ou concorrência entre entregas próximas.&lt;/p&gt;

&lt;p&gt;Sem uma estratégia de idempotência, a mesma intenção de pagamento pode disparar múltiplos jobs, criar registros duplicados ou repetir operações custosas.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Stack&lt;/h2&gt;

&lt;/div&gt;


&lt;ul&gt;

&lt;li&gt;PHP 8.3+ (Docker e CI em PHP 8.4)&lt;/li&gt;

&lt;li&gt;Laravel 13&lt;/li&gt;

&lt;li&gt;MySQL 8.4&lt;/li&gt;

&lt;li&gt;Redis 7…&lt;/li&gt;

&lt;/ul&gt;
&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/TarcisioAraujo7/idempotent-webhook-study-case" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


</description>
      <category>architecture</category>
      <category>backend</category>
      <category>database</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>Conheça o recycle(♻️) do Laravel</title>
      <dc:creator>Tarcisio Araújo </dc:creator>
      <pubDate>Wed, 05 Feb 2025 15:04:57 +0000</pubDate>
      <link>https://dev.to/tarcisioaraujo/conheca-o-recycle-do-laravel-3jpe</link>
      <guid>https://dev.to/tarcisioaraujo/conheca-o-recycle-do-laravel-3jpe</guid>
      <description>&lt;p&gt;Quando o assunto é criar estruturas de dados complexas para testes, o Laravel oferece uma ferramenta extremamente poderosa que pode ajudar a economizar recursos e possibilitar a criação de ambientes específicos, garantindo maior assertividade nos testes.&lt;/p&gt;

&lt;p&gt;Esta ferramenta é o método &lt;code&gt;recycle()&lt;/code&gt;, ele oferece o poder de reutilizar instâncias de models em chamadas a &lt;em&gt;Factories&lt;/em&gt;. Isso pode ser extremamente útil quando você precisa criar cenários com &lt;em&gt;Models&lt;/em&gt; contendo relações em comum. Por exemplo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Cria um Time&lt;/span&gt;
&lt;span class="nv"&gt;$team&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Team&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Cria 3 Vendedores associando-os ao mesmo Time através do recycle&lt;/span&gt;
&lt;span class="nv"&gt;$sellers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Seller&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;recycle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$team&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No exemplo acima, criamos um cenário específico com 3 instâncias de Vendedores associadas ao mesmo Time. Dessa forma, é possível realizar testes com essa estrutura de dados definida, sem a necessidade de configurar manualmente as relações através dos atributos do &lt;code&gt;create()&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Utilizando Collections 🗂️
&lt;/h2&gt;

&lt;p&gt;Além de funcionar com instâncias únicas de &lt;em&gt;Models&lt;/em&gt;, o método &lt;code&gt;recycle()&lt;/code&gt; também aceita &lt;em&gt;Collections&lt;/em&gt;. No entanto, quando utilizado com uma &lt;em&gt;Collection&lt;/em&gt;, seu comportamento difere do uso básico, oferecendo uma camada extra de flexibilidade na criação de cenários de teste.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$teams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Team&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nv"&gt;$sellers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Seller&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;recycle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$teams&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nesse trecho de código passamos uma &lt;em&gt;Collection&lt;/em&gt; com três Times distintos no &lt;em&gt;recycle&lt;/em&gt;. Desta forma, a cada Vendedor criado, será escolhido aleatoriamente um dos três Times para ser atribuído, construindo um ambiente controlado em que todos os Vendedores estão associados a um desses Times.&lt;/p&gt;




&lt;h2&gt;
  
  
  Como isso aumenta a performance do meu teste? 📈
&lt;/h2&gt;

&lt;p&gt;Ao criar conjuntos de dados com uma grande quantidade de registros, reciclar instâncias irá poupar recursos e diminuir o número de inserções no banco de dados.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$sellers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Seller&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nv"&gt;$sales&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Sale&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sem o uso do recycle, atingimos um total de 1003 queries! Sendo 3 para criar os primeiros Vendedores, 500 para criar cada Venda e  mais 500 para inserir, individualmente, os Vendedores associados a cada Venda.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$sellers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Seller&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nv"&gt;$sales&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Sale&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;recycle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$sellers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora, utilizando o recycle, conseguimos uma redução de 500 chamadas ao banco de dados! Eliminando as instâncias individuais de Vendedores realizadas pelas &lt;em&gt;Factory&lt;/em&gt; de Venda.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;Reutilizando instâncias de models, ele simplifica a criação de cenários complexos e reduz significativamente o número de queries realizadas. Resultando em testes mais rápidos, eficientes e fáceis de manter. O &lt;code&gt;recycle()&lt;/code&gt; acaba mostrando-se uma ferramenta essencial no repertório de qualquer desenvolvedor Laravel, especialmente para quem trabalha com desenvolvimento orientado a testes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Referência:
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://laravel.com/docs/11.x/eloquent-factories#recycling-an-existing-model-for-relationships" rel="noopener noreferrer"&gt;- Documentação oficial do Laravel.&lt;/a&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>laravel</category>
      <category>braziliandevs</category>
      <category>testing</category>
    </item>
    <item>
      <title>Simples Mudanças, Grandes Impactos: Como Fortalecer a Segurança da Sua Aplicação Laravel</title>
      <dc:creator>Tarcisio Araújo </dc:creator>
      <pubDate>Sun, 01 Sep 2024 14:10:22 +0000</pubDate>
      <link>https://dev.to/tarcisioaraujo/simples-mudancas-grandes-impactos-como-fortalecer-a-seguranca-da-sua-aplicacao-laravel-3ia0</link>
      <guid>https://dev.to/tarcisioaraujo/simples-mudancas-grandes-impactos-como-fortalecer-a-seguranca-da-sua-aplicacao-laravel-3ia0</guid>
      <description>&lt;p&gt;Se você já trabalhou em algum projeto Laravel com certeza já ter percebido como o framework entrega uma vasta gama de ferramentas uteis para o desenvolvimento. Mas, você já se perguntou se não pode estar deixando passar algum erro? Se ao utilizar essas ferramentas não acabou escapando algum detalhe simples, mas importante para garantir a segurança do seu sistema?&lt;/p&gt;

&lt;p&gt;Neste artigo irei mostrar alguns erros "simples", mas muito comuns e fáceis de serem resolvidos. &lt;/p&gt;




&lt;h2&gt;
  
  
  Fazendo um deploy seguro 🚀
&lt;/h2&gt;

&lt;p&gt;Ao tornar um projeto público na internet é necessário se atentar ao arquivo &lt;strong&gt;.env&lt;/strong&gt; que irá para os ambientes de produção/homologação, ou seja lá qual for o cenário de implantação, pois as chaves &lt;code&gt;APP_ENV&lt;/code&gt; e &lt;code&gt;APP_DEBUG&lt;/code&gt; podem estar abrindo possíveis vulnerabilidades nestes ambientes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Qual a utilidade dessas chaves?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;APP_ENV&lt;/strong&gt; = Flag que irá indicar o ambiente atual da aplicação, ela pode receber valores como "local", "production", "staging", "testing", etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;APP_DEBUG&lt;/strong&gt; = Flag que irá indicar se o ambiente atual está em modo de debug, ela pode receber um booleano.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use essa configuração apenas para ambiente de desenvolvimento.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;APP_ENV=local
APP_DEBUG=true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use essa configuração para ambiente de produção.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;APP_ENV=production
APP_DEBUG=false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Qual o problema de não usar a configuração certa em produção?
&lt;/h3&gt;

&lt;p&gt;Deixar uma aplicação Laravel no modo debug permite a exibição de mensagens de erros detalhadas, o que é ótimo para desenvolvimento mas, péssimo para outros ambientes, pois pode exibir as seguintes informações sensíveis:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Trechos de código.&lt;/li&gt;
&lt;li&gt;Versão do PHP e do Laravel. (Pode ser utilizado para explorar falhas de segurança conhecidas das versões)&lt;/li&gt;
&lt;li&gt;Informações do usuário e keys secretas.&lt;/li&gt;
&lt;li&gt;Senha do banco de dados.&lt;/li&gt;
&lt;li&gt;Estado da chave &lt;code&gt;APP_ENV&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A chave indicando o ambiente estar com o valor "local" pode abrir um leque amplo de vulnerabilidades, que pode ir desde o &lt;a href="https://laravel.com/docs/11.x/telescope" rel="noopener noreferrer"&gt;Telescope&lt;/a&gt;, que fica aberto sem necessidade de login até o comportamento de alguma funcionalidade do sistema esteja utilizando o APP_ENV para determinar o fluxo. Como, por exemplo, a seguinte configuração para o consumo de uma api externa:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="s1"&gt;'api_service'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'key'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'APP_ENV'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'production'&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'API_KEY_PROD'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'API_KEY_DEV'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Validando inputs 🔍
&lt;/h2&gt;

&lt;p&gt;Todo desenvolvedor já deve ter escutado em algum momento da vida "Não confie no usuário" e neste artigo você irá ver mais um motivo para pensar assim.&lt;/p&gt;

&lt;p&gt;Vamos imaginar o seguinte cenário, você possui em seu e-commerce um formulário de edição de produtos disponível apenas para os funcionários que não são gerentes, porém esse formulário não possui o campo &lt;code&gt;valor&lt;/code&gt; disponível a fim de não permitir alterações por funcionários não autorizados.&lt;/p&gt;

&lt;p&gt;Esta é a tabela &lt;em&gt;produtos&lt;/em&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;valor&lt;/th&gt;
&lt;th&gt;nome&lt;/th&gt;
&lt;th&gt;quantidade&lt;/th&gt;
&lt;th&gt;codigo&lt;/th&gt;
&lt;th&gt;setor_id&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;Panela&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;PAN10323&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Estas são as regras de validação do seu &lt;strong&gt;Form Request&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'nome'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'string|max:255'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'quantidade'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'integer|min:0'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'codigo'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'string|max:255|unique:itens,codigo'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'setor_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'exists:setores,id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E esta é a função utilizada no seu &lt;strong&gt;controller&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;FuncionarioUpdateItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;UpdateItemRequest&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Item&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;findOrFail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'sucesso'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Você verificou e os dados foram atualizados corretamente, as validações foram bem-sucedidas e tudo parece estar em ordem. No entanto, há uma grande falha nesse fluxo: estamos considerando que o formulário enviará apenas quatro campos na requisição. &lt;/p&gt;

&lt;p&gt;Mas o que acontece se um usuário mal-intencionado alterar manualmente o HTML da página para adicionar o campo &lt;strong&gt;&lt;em&gt;valor&lt;/em&gt;&lt;/strong&gt;? Ou se um invasor até mesmo enviar a requisição por meio de uma plataforma de API, como o Postman, incluindo o campo &lt;strong&gt;&lt;em&gt;valor&lt;/em&gt;&lt;/strong&gt; a requisição? &lt;/p&gt;

&lt;p&gt;Digamos que o usuário malicioso enviou os seguintes dados para a rota:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="err"&gt;valor:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="err"&gt;nome:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'Panela&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;barata'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Você iria ter a sua tabela &lt;em&gt;produtos&lt;/em&gt; com o seguinte registro: &lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;valor&lt;/th&gt;
&lt;th&gt;nome&lt;/th&gt;
&lt;th&gt;quantidade&lt;/th&gt;
&lt;th&gt;codigo&lt;/th&gt;
&lt;th&gt;setor_id&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Panela barata&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;PAN10323&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Agora o produto que custava R$ 100 acabou disponível por R$ 1, e o problema está na forma de extrair os dados do request.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Errado ❌&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Assim é retornado &lt;strong&gt;TODOS&lt;/strong&gt; os dados do request.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Certo ✅&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;validated&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Assim é retornado &lt;strong&gt;APENAS&lt;/strong&gt; os dados validos do request.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Se o &lt;strong&gt;Controller&lt;/strong&gt; do exemplo estivesse utilizando a forma correta de receber os dados, o mesmo exemplo de payload malicioso iria resultar no seguinte registro: &lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;valor&lt;/th&gt;
&lt;th&gt;nome&lt;/th&gt;
&lt;th&gt;quantidade&lt;/th&gt;
&lt;th&gt;codigo&lt;/th&gt;
&lt;th&gt;setor_id&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;Panela barata&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;PAN10323&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Evitando assim qualquer dado não esperado!&lt;/p&gt;




&lt;h2&gt;
  
  
  Evitando SQL Injection 💉
&lt;/h2&gt;

&lt;p&gt;Quando falamos de SQL Injection falamos de vulnerabilidades que dão possibilidade para invasores executar comandos SQL indesejados e imprevistos no banco de dados da aplicação. &lt;/p&gt;

&lt;p&gt;Exemplo:&lt;br&gt;
Considere que seu sistema possui uma funcionalidade de pesquisa de usuários que executa a seguinte query.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"SELECT * FROM users WHERE username = '&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;'"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Considere que a variável $user é dada pelo usuário realizando a busca, se o usuário inserir &lt;code&gt;admin'; DROP TABLE users; --&lt;/code&gt; irá gerar a seguinte consulta.&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;users&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'admin'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;--'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A consulta gerada é maliciosa e irá derrubar a tabela &lt;code&gt;users&lt;/code&gt; inteira gerando um dano gigantesco a integridade da aplicação.&lt;/p&gt;

&lt;h3&gt;
  
  
  Como proteger minha aplicação?
&lt;/h3&gt;

&lt;p&gt;Os próprios &lt;a href="https://laravel.com/docs/11.x/eloquent" rel="noopener noreferrer"&gt;Eloquent ORM&lt;/a&gt; e o &lt;a href="https://laravel.com/docs/11.x/queries" rel="noopener noreferrer"&gt;Query Builder&lt;/a&gt; do Laravel possuem uma proteção robusta contra inputs maliciosos e caracteres potencialmente perigosos para suas consultas. &lt;strong&gt;Porem devemos tomar cuidado ao utilizar quaisquer métodos que aceitem queries raw&lt;/strong&gt;, pois estes se mal utilizados, podem resultar em aberturas para injeção de inputs mal-intencionados.&lt;/p&gt;

&lt;p&gt;Exemplos:&lt;br&gt;
&lt;strong&gt;Errado ❌&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"SELECT * FROM users WHERE email = '"&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$email&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"'"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Neste caso estamos passando diretamente o input do usuário para a consulta via concatenação de strings, que não irá realizar nenhuma sanitização de parâmetros.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Certo ✅&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'email'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Desta forma utilizamos a proteção nativa do Laravel, passando o input como parâmetro da função que irá construir a query.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Certo ✅&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"SELECT * FROM users WHERE email = ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$mail&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Supondo que você tenha realmente a necessidade de utilizar um método que aceite consulta raw, passe os parâmetros através de bindings. Desta forma, o Laravel irá impedir que o input seja interpretado como código SQL e manter a query segura.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Não se esqueça ⚠&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Sempre&lt;/strong&gt; use bindings quando o assunto for consultas com SQL bruto no Laravel. &lt;/p&gt;




&lt;h2&gt;
  
  
  Utilizando Rate Limit 🚧
&lt;/h2&gt;

&lt;p&gt;Limitar quantas requisições podem ser realizadas em um determinado tempo é essencial para proteger a sua aplicação contra alguns tipos de ataques, como, por exemplo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Brute Force:&lt;/strong&gt; Tentativa de "adivinhar" senhas e credenciais realizando inúmeras requisições com combinações possíveis até obter sucesso.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DDoS:&lt;/strong&gt; Tentativa de sobrecarregar o servidor com uma quantidade massiva de requisições em um curto período.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;É recomendável considerar adicionar esta proteção em rotas relacionadas a formas de autenticação ou rotas com uso de token aleatório privado. &lt;/p&gt;

&lt;h3&gt;
  
  
  Como aplicar?
&lt;/h3&gt;

&lt;p&gt;No arquivo &lt;code&gt;AppServiceProvider.php&lt;/code&gt; você irá criar dentro da função &lt;code&gt;boot&lt;/code&gt; o seu RateLimiter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;RateLimiter&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'default-limiter'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Limit&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;perMinute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pronto, agora com nosso limiter definido iremos aplicar a nossa rota sensível.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'throttle:default-limiter'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/private/{token}'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;TokenController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'privateThing'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Considere que esta rota recebe um token gerado aleatoriamente para exibir um dado privado do seu portador. Desta forma estamos definindo um limite de 10 requisições por minuto, evitando um possível ataque por força bruta para adivinhar o token secreto e assim obter acesso à informação privada.&lt;/p&gt;

&lt;p&gt;Ainda é possível definir regras específicas e personalizadas para a limitação como, por exemplo, por IP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;RateLimiter&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'default-limiter'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Limit&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;perMinute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essas limitações são usualmente baseadas em IP e no usuário logado, e são muito importantes para evitar diversos tipos de ataques, inclusive ataques DDoS feito com diversos IPs. Lembre-se sempre de criar regras de limitação robustas!&lt;/p&gt;

&lt;p&gt;Se você já utiliza algum kit de autenticação pronto do Laravel como o Breeze ou o Jetstream então parabéns! Suas rotas de login já estão protegidas com Rate Limit.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Atenção ⚠&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Preste bastante atenção ao definir as limitações para não afetar o uso normal dos usuários do seu sistema. Em casos de rotas com um fluxo maior de requests é recomendável utilizar o &lt;a href="https://laravel.com/docs/11.x/releases#rate-limiting" rel="noopener noreferrer"&gt;Per-Second Rate Limiting&lt;/a&gt; feature do Laravel 11. &lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;Devemos manter sempre um olhar atento para questões de segurança durante o desenvolvimento, pois pequenas decisões ou deslizes aparentemente insignificantes podem resultar em grandes problemas no futuro.&lt;/p&gt;

&lt;p&gt;Muito obrigado por ler até aqui! ❤&lt;/p&gt;

</description>
      <category>php</category>
      <category>laravel</category>
      <category>security</category>
      <category>braziliandevs</category>
    </item>
  </channel>
</rss>
