<?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: Diego Broetto</title>
    <description>The latest articles on DEV Community by Diego Broetto (@broettod).</description>
    <link>https://dev.to/broettod</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%2F2972664%2F3a9f248e-ec85-4254-b34c-7e3dbe50465e.jpg</url>
      <title>DEV Community: Diego Broetto</title>
      <link>https://dev.to/broettod</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/broettod"/>
    <language>en</language>
    <item>
      <title>Event-driven com EventBridge + SQS: quando cada um entra e como evitar os erros mais comuns</title>
      <dc:creator>Diego Broetto</dc:creator>
      <pubDate>Tue, 13 Oct 2026 23:06:03 +0000</pubDate>
      <link>https://dev.to/broettod/event-driven-com-eventbridge-sqs-quando-cada-um-entra-e-como-evitar-os-erros-mais-comuns-36lb</link>
      <guid>https://dev.to/broettod/event-driven-com-eventbridge-sqs-quando-cada-um-entra-e-como-evitar-os-erros-mais-comuns-36lb</guid>
      <description>&lt;p&gt;Ouço muito a pergunta "EventBridge ou SQS?". Quase sempre ela está mal formulada.&lt;/p&gt;

&lt;p&gt;Na maioria dos fluxos com mais de um consumidor, os dois trabalham juntos: o EventBridge decide para onde o evento vai, e o SQS segura esse evento até alguém poder processar. Roteamento e durabilidade são problemas diferentes, então um não substitui o outro.&lt;/p&gt;

&lt;p&gt;Este artigo mostra onde cada peça entra, o que cada uma resolve sozinha e quais erros aparecem quando alguém usa as duas sem saber por quê.&lt;/p&gt;




&lt;h2&gt;
  
  
  1.0 O que cada serviço realmente resolve
&lt;/h2&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F35smit4x8fbam6qxyf58.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F35smit4x8fbam6qxyf58.png" alt="Comparação entre o papel do EventBridge (roteamento) e do SQS (durabilidade) em um fluxo de eventos" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Os dois aparecem juntos em quase toda discussão porque ambos recebem mensagens. Mas o trabalho de cada um é diferente: o EventBridge decide para onde a informação vai, e o SQS guarda a informação até alguém processá-la.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.1 EventBridge
&lt;/h3&gt;

&lt;p&gt;O EventBridge é um barramento de eventos. Você publica um evento uma vez, e ele avalia as regras contra o conteúdo desse evento para decidir quais alvos recebem. O produtor não sabe quantos consumidores existem nem quem são. Para incluir um consumidor novo, basta criar uma regra. O código de quem publica não muda.&lt;/p&gt;

&lt;p&gt;O que ele faz bem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Entregar o mesmo evento a vários alvos&lt;/li&gt;
&lt;li&gt;Filtrar por conteúdo com event patterns, usando campos como &lt;code&gt;source&lt;/code&gt;, &lt;code&gt;detail-type&lt;/code&gt; ou valores dentro de &lt;code&gt;detail&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Receber eventos de serviços AWS e de parceiros SaaS sem código de integração&lt;/li&gt;
&lt;li&gt;Arquivar eventos e reenviá-los depois (archive e replay)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;O que ele não faz:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Garantir ordem entre eventos&lt;/li&gt;
&lt;li&gt;Segurar um evento por tempo indeterminado. Por padrão, ele tenta entregar ao alvo por até 24 horas e até 185 vezes. Esgotadas as tentativas, se o alvo não tem DLQ, o evento é descartado.&lt;/li&gt;
&lt;li&gt;Controlar o ritmo do consumidor. Ele empurra o evento para o alvo, então quem precisa aguentar o volume é o alvo.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  1.2 SQS
&lt;/h3&gt;

&lt;p&gt;O SQS é uma fila. A mensagem fica guardada até um consumidor processá-la e apagá-la de forma explícita. Se o consumidor cair no meio do processamento, a mensagem volta a ficar visível depois do visibility timeout, e outro consumidor pode pegá-la.&lt;/p&gt;

&lt;p&gt;O que ele faz bem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Absorver picos. A fila cresce durante o pico, e o consumidor processa no ritmo que aguenta.&lt;/li&gt;
&lt;li&gt;Limitar a concorrência do consumidor (no Lambda, com &lt;code&gt;MaximumConcurrency&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Reter mensagens por até 14 dias (o padrão é 4)&lt;/li&gt;
&lt;li&gt;Mover para uma DLQ as mensagens que falham de forma persistente, depois de &lt;code&gt;maxReceiveCount&lt;/code&gt; tentativas&lt;/li&gt;
&lt;li&gt;Manter a ordem das mensagens, quando você usa FIFO&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;O que ele não faz:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rotear. Quem publica escolhe a fila de destino.&lt;/li&gt;
&lt;li&gt;Entregar a mesma mensagem a vários consumidores. Consumidores na mesma fila competem pelas mensagens. Para broadcast, o caminho é uma fila por assinante, com SNS ou EventBridge na frente.&lt;/li&gt;
&lt;li&gt;Garantir entrega única na fila padrão. Ela é at-least-once, então o consumidor precisa ser idempotente. A FIFO tem deduplicação, mas só dentro de uma janela de tempo.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Falando em FIFO, estou pensando em fazer uma série de artigos voltados a fundamentos da computação, coisas como Filas, Pilhas, Listas, Programação Orientada a Objetos, entre outros... O que vocês acham?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  1.3 Quando usar cada um
&lt;/h3&gt;

&lt;p&gt;Vale reparar nos sentidos do fluxo: a regra do EventBridge empurra o evento para a fila, mas quem lê a fila é o Lambda, que faz polling pelo event source mapping.&lt;/p&gt;

&lt;p&gt;Para decidir, pergunte duas coisas: quem precisa receber esse evento? E quem precisa segurá-lo até estar pronto para processar?&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Necessidade&lt;/th&gt;
&lt;th&gt;Use&lt;/th&gt;
&lt;th&gt;Por quê&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Um evento para vários consumidores que não se conhecem&lt;/td&gt;
&lt;td&gt;EventBridge&lt;/td&gt;
&lt;td&gt;O produtor só publica. Cada consumidor cria a própria regra.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Absorver picos antes do processamento&lt;/td&gt;
&lt;td&gt;SQS&lt;/td&gt;
&lt;td&gt;A fila segura o excesso, e o consumidor anda no próprio ritmo.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Processar em ordem estrita&lt;/td&gt;
&lt;td&gt;SQS FIFO&lt;/td&gt;
&lt;td&gt;Só a FIFO garante ordem dentro de um grupo de mensagens.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rotear pelo conteúdo do evento&lt;/td&gt;
&lt;td&gt;EventBridge&lt;/td&gt;
&lt;td&gt;Event patterns decidem o alvo sem código.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reprocessar se o consumidor cair no meio&lt;/td&gt;
&lt;td&gt;SQS&lt;/td&gt;
&lt;td&gt;A mensagem permanece na fila até ser apagada.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Consumidor rápido, de baixo volume, sem necessidade de controlar o ritmo&lt;/td&gt;
&lt;td&gt;EventBridge direto para a Lambda&lt;/td&gt;
&lt;td&gt;O Lambda já guarda o evento numa fila interna e tenta de novo. Uma fila SQS na frente seria mais uma peça sem ganho.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Um único consumidor, sem roteamento&lt;/td&gt;
&lt;td&gt;SQS&lt;/td&gt;
&lt;td&gt;O EventBridge seria mais uma peça para manter, sem trazer ganho.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A maioria dos fluxos que precisam de mais de um consumidor acaba usando as duas peças: o EventBridge decide quem recebe, e o SQS segura o evento até o consumidor estar pronto. É o padrão da seção 2.0.&lt;/p&gt;




&lt;h2&gt;
  
  
  2.0 O padrão mais comum: EventBridge → SQS → Lambda
&lt;/h2&gt;

&lt;p&gt;Por que não colocar a Lambda como alvo direto da regra do EventBridge? Funciona, e é mais protegido do que parece. O EventBridge invoca o Lambda de forma assíncrona: o evento entra numa fila interna do Lambda, que continua tentando por até 6 horas quando a função sofre throttling. Se a função der erro, são mais duas tentativas.&lt;/p&gt;

&lt;p&gt;O que essa fila interna não oferece é controle. Você não enxerga quantos eventos estão esperando, não escolhe quantos são processados ao mesmo tempo (a não ser reservando concorrência para a função inteira) e não processa em lote. Com o SQS no meio, o backlog vira métrica (&lt;code&gt;ApproximateNumberOfMessagesVisible&lt;/code&gt; e &lt;code&gt;ApproximateAgeOfOldestMessage&lt;/code&gt;), a concorrência do consumidor fica no &lt;code&gt;MaximumConcurrency&lt;/code&gt; do event source mapping, e a mensagem pode esperar até 14 dias.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz9jw564qa6yzri569bq3.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz9jw564qa6yzri569bq3.png" alt="Diagrama do padrão EventBridge, SQS e Lambda, com o backlog visível na fila" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;O desenho é simples. O que costuma dar errado é a configuração de cada peça: visibility timeout, permissões entre EventBridge e SQS, comportamento de lote na Lambda. O resto do artigo cobre isso com um projeto que reproduz cada erro de propósito.&lt;/p&gt;




&lt;h2&gt;
  
  
  3.0 O cenário: um evento, quatro destinos
&lt;/h2&gt;

&lt;p&gt;O &lt;code&gt;PedidoCriado&lt;/code&gt; do projeto de pedidos agora é roteado (fan-out) para três filas, uma por domínio, e direto para a Lambda que classifica o pedido, sem fila no meio:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                     ┌─→ SQS Estoque     → Lambda (baixa)
PedidoCriado         ├─→ SQS Notificação → Lambda (e-mail)
  → EventBridge ─────┼─→ SQS Financeiro  → Lambda (cobrança)
                     └─→ Lambda processar_pedido (sem fila)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fodcd2jnkdawsvcqlzjl6.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fodcd2jnkdawsvcqlzjl6.png" alt="Regras do bus de eventos no console do EventBridge, com um evento roteado para quatro destinos" width="799" height="287"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Um fluxo linear, com uma fila e um consumidor, prova pouco. Dá para fazer isso só com SQS e ninguém notaria a diferença. O fan-out é o argumento a favor do EventBridge: o mesmo evento alimenta estoque, notificação e faturamento, e nenhum desses consumidores sabe que os outros existem. Se amanhã surgir um quinto consumidor, como um serviço de detecção de fraude, ele assina o evento e pronto. O código que cria o pedido não muda uma linha.&lt;/p&gt;

&lt;p&gt;Cada fila do projeto reproduz de propósito um erro comum.&lt;/p&gt;


&lt;h2&gt;
  
  
  4.0 Visibility timeout: a fila reentregando antes da hora
&lt;/h2&gt;

&lt;p&gt;A fila de Estoque simula uma chamada a um sistema legado, com latência entre 3 e 15 segundos por mensagem.&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;# Simula latência de um sistema de estoque legado
&lt;/span&gt;&lt;span class="n"&gt;latencia&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LATENCIA_MIN_SEGUNDOS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LATENCIA_MAX_SEGUNDOS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;latencia&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;O visibility timeout da fila é de 180 segundos, para uma função com timeout de 30. São as 6x que a documentação do Lambda recomenda para filas que alimentam uma função.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;EstoqueQueue&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::SQS::Queue&lt;/span&gt;
  &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;VisibilityTimeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;180&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Validando o projeto, apareceu um detalhe que eu não tinha previsto. A primeira versão lia lotes de 5 mensagens. Com cada baixa levando até 15 segundos, o pior caso de um lote chegava a 75 segundos, bem acima do timeout de 30 da função. Troquei para &lt;code&gt;BatchSize: 1&lt;/code&gt;. Quando cada mensagem é lenta, o tamanho do lote também entra na conta do timeout.&lt;/p&gt;

&lt;p&gt;O que acontece se você reduzir o visibility timeout para 10 segundos? O SQS entrega a mensagem ao consumidor e começa a contar. Se a mensagem não for apagada dentro desses 10 segundos, ela volta a ficar visível e pode ser entregue de novo, possivelmente enquanto o primeiro consumidor ainda está no meio do &lt;code&gt;time.sleep()&lt;/code&gt;. Com isso, dois consumidores processam o mesmo pedido e o estoque é debitado duas vezes.&lt;/p&gt;

&lt;p&gt;No projeto dá para reproduzir: troque o &lt;code&gt;VisibilityTimeout&lt;/code&gt; de 180 para 10 no template, faça o deploy e compare a métrica &lt;code&gt;EstoqueBaixado&lt;/code&gt; com o número de pedidos criados.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxhieth2ltxc6698pooka.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxhieth2ltxc6698pooka.png" alt="Métrica EstoqueBaixado comparada ao número de pedidos criados, com mensagens processadas em duplicidade" width="800" height="406"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  5.0 Lote com falhas: o que volta para a fila e o que some
&lt;/h2&gt;

&lt;p&gt;A fila de Notificação processa lotes de até 10 mensagens por invocação. O problema aparece quando uma mensagem do lote falha.&lt;/p&gt;

&lt;p&gt;Sem tratamento especial, o comportamento é tudo ou nada. Se uma exceção sobe do handler, o lote inteiro é considerado falho e as 10 mensagens voltam a ficar visíveis, inclusive as 9 que já tinham mandado o e-mail de confirmação ao cliente.&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;# Versão ingênua, evite
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Records&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="nf"&gt;enviar_notificacao&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# se UMA lançar exceção, TODAS voltam
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A correção é a resposta parcial de lote, com o utilitário de batch do Powertools:&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;aws_lambda_powertools.utilities.batch&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;BatchProcessor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;EventType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;process_partial_response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;processor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BatchProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;EventType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SQS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&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;process_partial_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;record_handler&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;processar_registro&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;context&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;O &lt;code&gt;BatchProcessor&lt;/code&gt; processa cada mensagem isoladamente, captura as falhas por item e devolve a lista &lt;code&gt;batchItemFailures&lt;/code&gt; com os IDs que falharam. Só essas voltam para a fila. Se todas as mensagens do lote falharem, ele lança &lt;code&gt;BatchProcessingError&lt;/code&gt;, e aí o lote inteiro volta.&lt;/p&gt;

&lt;p&gt;Além do código, o event source mapping precisa da flag:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;SQSEvent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SQS&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Queue&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;NotificacaoQueue.Arn&lt;/span&gt;
      &lt;span class="na"&gt;BatchSize&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
      &lt;span class="na"&gt;FunctionResponseTypes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;ReportBatchItemFailures&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fui34as38pi9z9cn7yykd.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fui34as38pi9z9cn7yykd.png" alt="Trigger SQS da Lambda de notificação com Report batch item failures ativo" width="800" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sem a flag, o cenário é pior que o da versão ingênua. O handler captura as falhas e termina sem erro, e a documentação do Lambda diz que, quando a função processa o lote com sucesso, o Lambda apaga as mensagens da fila. A lista de falhas vira um valor de retorno qualquer, e as mensagens que falharam somem sem passar pela DLQ.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F92xkurwmrf2fxxxafxlx.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F92xkurwmrf2fxxxafxlx.png" alt="Resultado no Logs Insights mostrando mensagens que falharam e não voltaram para a fila" width="800" height="326"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;É um erro fácil de cometer porque o código parece certo, e a outra metade da correção está no template. No projeto, o consumer de notificação simula 25% de falha aleatória, o suficiente para gerar lotes mistos com frequência.&lt;/p&gt;


&lt;h2&gt;
  
  
  6.0 DLQ: separando erro passageiro de erro permanente
&lt;/h2&gt;

&lt;p&gt;A fila de Financeiro simula outro tipo de falha: um erro de negócio permanente, como um valor de cobrança inválido, que nenhum retry corrige.&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;TAXA_ERRO_PERMANENTE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;InvalidOperation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Valor de cobrança inválido para pedido &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pedido_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;valor&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A diferença importa. Para falha passageira (timeout de rede, serviço fora do ar por alguns segundos), retry é o comportamento certo. Para falha permanente, retry só atrasa o inevitável e gasta concorrência. A fila tenta 3 vezes antes de desistir:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;FinanceiroQueue&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::SQS::Queue&lt;/span&gt;
  &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;VisibilityTimeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;90&lt;/span&gt;   &lt;span class="c1"&gt;# 6x o timeout de 15s da função&lt;/span&gt;
    &lt;span class="na"&gt;RedrivePolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;deadLetterTargetArn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;FinanceiroDLQ.Arn&lt;/span&gt;
      &lt;span class="na"&gt;maxReceiveCount&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Depois da terceira entrega sem sucesso, o SQS move a mensagem para a DLQ sozinho. Não precisa de código.&lt;/p&gt;

&lt;p&gt;Nessa fila, o erro mais comum é esquecer o alarme na própria DLQ:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;FinanceiroDLQAlarm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::CloudWatch::Alarm&lt;/span&gt;
  &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS/SQS&lt;/span&gt;
    &lt;span class="na"&gt;MetricName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ApproximateNumberOfMessagesVisible&lt;/span&gt;
    &lt;span class="na"&gt;Dimensions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;QueueName&lt;/span&gt;
        &lt;span class="na"&gt;Value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;FinanceiroDLQ.QueueName&lt;/span&gt;
    &lt;span class="na"&gt;Statistic&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Maximum&lt;/span&gt;
    &lt;span class="na"&gt;Period&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;300&lt;/span&gt;
    &lt;span class="na"&gt;EvaluationPeriods&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
    &lt;span class="na"&gt;Threshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
    &lt;span class="na"&gt;ComparisonOperator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GreaterThanThreshold&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Sem o alarme, as mensagens se acumulam na DLQ sem ninguém ser avisado, e o problema só aparece quando alguém nota que uma parte dos pedidos nunca gerou cobrança. Até lá, podem ser semanas de mensagens para investigar uma por uma.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fanrvt5ottmwrap21r5oq.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fanrvt5ottmwrap21r5oq.png" alt="Fila de Financeiro com a DLQ recebendo mensagens e o alarme em estado ALARM" width="800" height="344"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  7.0 Quando o EventBridge sozinho basta
&lt;/h2&gt;

&lt;p&gt;Nem todo consumidor precisa de fila no meio. O &lt;code&gt;processar_pedido&lt;/code&gt;, que classifica o pedido por faixa de valor, é rápido, tem volume baixo e não precisa de controle de ritmo. Para ele, a fila interna do Lambda dá conta, e uma fila SQS na frente seria só mais uma peça para manter.&lt;/p&gt;

&lt;p&gt;No projeto, a quarta rota leva o &lt;code&gt;PedidoCriado&lt;/code&gt; direto para essa função:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;ProcessarPedidoFunction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless::Function&lt;/span&gt;
  &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;PedidoCriado&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;EventBridgeRule&lt;/span&gt;
        &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;EventBusName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;PedidosEventBus&lt;/span&gt;
          &lt;span class="na"&gt;Pattern&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;observability-demo.pedidos&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
            &lt;span class="na"&gt;detail-type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;PedidoCriado&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
          &lt;span class="na"&gt;DeadLetterConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;Arn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;RuleDLQ.Arn&lt;/span&gt;
    &lt;span class="na"&gt;EventInvokeConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;MaximumRetryAttempts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
      &lt;span class="na"&gt;DestinationConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;OnFailure&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SQS&lt;/span&gt;
          &lt;span class="na"&gt;Destination&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;ProcessarFalhasQueue.Arn&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Sem fila no meio, essa rota precisa de duas redes de segurança, uma de cada lado da entrega.&lt;/p&gt;

&lt;p&gt;A primeira é o &lt;code&gt;DeadLetterConfig&lt;/code&gt; na regra, que pega falha de entrega. O EventBridge tentou invocar a função por até 24 horas e não conseguiu, por exemplo por causa de uma permissão alterada sem ninguém notar.&lt;/p&gt;

&lt;p&gt;A segunda é o destino &lt;code&gt;OnFailure&lt;/code&gt; no Lambda. Quando o Lambda aceita o evento na fila interna, a entrega do EventBridge está concluída. Se depois a função errar na primeira tentativa e nas duas seguintes, o Lambda descarta o evento, e a DLQ da regra não fica sabendo de nada. O &lt;code&gt;OnFailure&lt;/code&gt; manda esse evento para a &lt;code&gt;ProcessarFalhasQueue&lt;/code&gt; em vez de jogá-lo fora. É fácil configurar só a primeira rede e achar que a rota está protegida.&lt;/p&gt;

&lt;p&gt;Nas rotas com fila, a divisão é parecida. A &lt;code&gt;RuleDLQ&lt;/code&gt; também protege os três alvos SQS contra falha de entrega, e a falha de processamento fica com a DLQ de cada fila, pelo &lt;code&gt;maxReceiveCount&lt;/code&gt; da seção 6.0.&lt;/p&gt;


&lt;h2&gt;
  
  
  8.0 A policy da fila, que só aparece quando falta
&lt;/h2&gt;

&lt;p&gt;O fan-out não funciona sem um passo que é fácil de pular: o EventBridge precisa de permissão explícita para escrever em cada fila.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;EstoqueQueuePolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::SQS::QueuePolicy&lt;/span&gt;
  &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Queues&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="nv"&gt;EstoqueQueue&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;PolicyDocument&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Statement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Effect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Allow&lt;/span&gt;
          &lt;span class="na"&gt;Principal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;Service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;events.amazonaws.com&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sqs:SendMessage&lt;/span&gt;
          &lt;span class="na"&gt;Resource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;EstoqueQueue.Arn&lt;/span&gt;
          &lt;span class="na"&gt;Condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;ArnEquals&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aws:SourceArn"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="nv"&gt;EstoqueRule.Arn&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Sem essa policy, a regra é criada, o alvo aparece configurado, e os eventos não chegam. O produtor não vê erro nenhum, porque o &lt;code&gt;PutEvents&lt;/code&gt; deu certo: a falha acontece depois, na entrega. É o tipo de falha que a &lt;code&gt;RuleDLQ&lt;/code&gt; da seção 7.0 captura. Sem ela, você só descobre quando o time de um domínio reclama que não recebe nada.&lt;/p&gt;

&lt;p&gt;A condição &lt;code&gt;ArnEquals&lt;/code&gt; limita a permissão àquela regra. Em bus customizado, o ARN da regra inclui o nome do bus (&lt;code&gt;rule/pedidos-bus-dev/nome-da-regra&lt;/code&gt;), então usar &lt;code&gt;!GetAtt EstoqueRule.Arn&lt;/code&gt; evita montar esse ARN à mão e errar o formato.&lt;/p&gt;


&lt;h2&gt;
  
  
  9.0 Checklist antes de ir para produção
&lt;/h2&gt;

&lt;p&gt;Use esta lista como revisão do template antes do primeiro deploy em produção. Cada item é uma falha que aparece nos cenários deste artigo.&lt;/p&gt;
&lt;h3&gt;
  
  
  9.1 Filas SQS
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Visibility timeout de pelo menos 6x o timeout da função consumidora, em cada fila&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;BatchSize&lt;/code&gt; compatível com o timeout: lote vezes o tempo de cada mensagem cabe na função&lt;/li&gt;
&lt;li&gt;DLQ configurada em toda fila que alimenta um consumidor crítico&lt;/li&gt;
&lt;li&gt; &lt;code&gt;maxReceiveCount&lt;/code&gt; calibrado: baixo o bastante para não represar retry inútil, alto o bastante para sobreviver a uma falha passageira&lt;/li&gt;
&lt;li&gt;Resource policy da fila liberando &lt;code&gt;sqs:SendMessage&lt;/code&gt; só para a regra específica, com &lt;code&gt;aws:SourceArn&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  9.2 Consumidor Lambda
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ReportBatchItemFailures&lt;/code&gt; ligado no event source mapping, e não só no código&lt;/li&gt;
&lt;li&gt;Handler com &lt;code&gt;BatchProcessor&lt;/code&gt; (ou lógica equivalente) devolvendo &lt;code&gt;batchItemFailures&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Consumidor idempotente, porque a fila padrão entrega pelo menos uma vez&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MaximumConcurrency&lt;/code&gt; definido onde o sistema downstream não aguenta o pico&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  9.3 EventBridge
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;DeadLetterConfig&lt;/code&gt; em cada alvo da regra, principalmente quando não há fila no meio&lt;/li&gt;
&lt;li&gt;Destino &lt;code&gt;OnFailure&lt;/code&gt; (ou DLQ da função) quando a regra invoca a Lambda direto&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  9.4 Alarmes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Alarme em &lt;code&gt;ApproximateNumberOfMessagesVisible&lt;/code&gt; de cada DLQ, incluindo a da regra&lt;/li&gt;
&lt;li&gt;Alarme em &lt;code&gt;ApproximateAgeOfOldestMessage&lt;/code&gt; das filas principais, para detectar consumidor atrasado&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  10.0 Projeto de exemplo
&lt;/h2&gt;

&lt;p&gt;O repositório estende o &lt;code&gt;observability-demo&lt;/code&gt; dos artigos anteriores com o fan-out completo: três filas com seus consumidores, a rota direta para a Lambda de classificação, as policies e os alarmes descritos acima. Cada consumer tem um comentário no código explicando a lição daquela fila.&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/diegobroetto" rel="noopener noreferrer"&gt;
        diegobroetto
      &lt;/a&gt; / &lt;a href="https://github.com/diegobroetto/observability-demo" rel="noopener noreferrer"&gt;
        observability-demo
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Demo serverless de observabilidade com CloudWatch + X-Ray (logs, metricas EMF, traces) - AWS SAM
    &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;observability-demo&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;Exemplo completo de observabilidade serverless com &lt;strong&gt;CloudWatch + X-Ray&lt;/strong&gt; para o artigo:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Observabilidade serverless sem surpresa no bolso: logs, métricas e traces com CloudWatch + X-Ray do jeito certo"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;O que este projeto demonstra&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;&lt;/p&gt;&lt;div class="table-wrapper-paragraph"&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;table&gt;

&lt;thead&gt;

&lt;tr&gt;

&lt;th&gt;Pilar&lt;/th&gt;

&lt;th&gt;Ferramenta&lt;/th&gt;

&lt;th&gt;Onde ver no código&lt;/th&gt;

&lt;/tr&gt;

&lt;/thead&gt;

&lt;tbody&gt;

&lt;tr&gt;

&lt;td&gt;&lt;strong&gt;Logs estruturados&lt;/strong&gt;&lt;/td&gt;

&lt;td&gt;Powertools Logger + JSON nativo do Lambda&lt;/td&gt;

&lt;td&gt;
&lt;br&gt;
&lt;code&gt;observability.py&lt;/code&gt;, todos os handlers&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;&lt;strong&gt;Retenção de logs&lt;/strong&gt;&lt;/td&gt;

&lt;td&gt;AWS::Logs::LogGroup com RetentionInDays&lt;/td&gt;

&lt;td&gt;
&lt;br&gt;
&lt;code&gt;template.yaml&lt;/code&gt; → LogGroups&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;&lt;strong&gt;Log level por ambiente&lt;/strong&gt;&lt;/td&gt;

&lt;td&gt;Mappings no SAM + LOG_EVENT flag&lt;/td&gt;

&lt;td&gt;
&lt;br&gt;
&lt;code&gt;template.yaml&lt;/code&gt; → Mappings&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;&lt;strong&gt;Métricas sem custo extra&lt;/strong&gt;&lt;/td&gt;

&lt;td&gt;Embedded Metric Format (EMF)&lt;/td&gt;

&lt;td&gt;&lt;code&gt;criar_pedido/handler.py&lt;/code&gt;&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;&lt;strong&gt;Métricas com dimensão de negócio&lt;/strong&gt;&lt;/td&gt;

&lt;td&gt;Powertools Metrics + add_dimension&lt;/td&gt;

&lt;td&gt;&lt;code&gt;processar_pedido/handler.py&lt;/code&gt;&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;&lt;strong&gt;Traces por subsegmento&lt;/strong&gt;&lt;/td&gt;

&lt;td&gt;Powertools Tracer + @capture_method&lt;/td&gt;

&lt;td&gt;Todos os handlers&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;&lt;strong&gt;Anotações no X-Ray&lt;/strong&gt;&lt;/td&gt;

&lt;td&gt;tracer.put_annotation&lt;/td&gt;

&lt;td&gt;&lt;code&gt;consultar_pedido/handler.py&lt;/code&gt;&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;&lt;strong&gt;Alarmes em p99&lt;/strong&gt;&lt;/td&gt;

&lt;td&gt;AWS::CloudWatch::Alarm + ExtendedStatistic&lt;/td&gt;

&lt;td&gt;
&lt;br&gt;
&lt;code&gt;template.yaml&lt;/code&gt; → Alarmes&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;&lt;strong&gt;Dashboard consolidado&lt;/strong&gt;&lt;/td&gt;

&lt;td&gt;AWS::CloudWatch::Dashboard&lt;/td&gt;

&lt;td&gt;
&lt;br&gt;
&lt;code&gt;template.yaml&lt;/code&gt; → Dashboard&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;&lt;strong&gt;Queries prontas&lt;/strong&gt;&lt;/td&gt;

&lt;td&gt;CloudWatch Logs Insights&lt;/td&gt;

&lt;td&gt;&lt;code&gt;monitoring/insights-queries.md&lt;/code&gt;&lt;/td&gt;

&lt;/tr&gt;

&lt;/tbody&gt;

&lt;/table&gt;&lt;/div&gt;&lt;br&gt;
&lt;p&gt;&lt;/p&gt;


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

&lt;p&gt;&lt;/p&gt;&lt;div class="table-wrapper-paragraph"&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;table&gt;

&lt;thead&gt;

&lt;tr&gt;

&lt;th&gt;Método&lt;/th&gt;

&lt;th&gt;Path&lt;/th&gt;

&lt;th&gt;Função&lt;/th&gt;

&lt;th&gt;Descrição&lt;/th&gt;

&lt;/tr&gt;

&lt;/thead&gt;

&lt;tbody&gt;

&lt;tr&gt;

&lt;td&gt;POST&lt;/td&gt;

&lt;td&gt;&lt;code&gt;/pedidos&lt;/code&gt;&lt;/td&gt;

&lt;td&gt;CriarPedidoFunction&lt;/td&gt;

&lt;td&gt;Cria o pedido e publica &lt;code&gt;PedidoCriado&lt;/code&gt; no EventBridge&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;GET&lt;/td&gt;

&lt;td&gt;&lt;code&gt;/pedidos/{id}&lt;/code&gt;&lt;/td&gt;

&lt;/tr&gt;

&lt;/tbody&gt;

&lt;/table&gt;&lt;/div&gt;…&lt;p&gt;&lt;/p&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/diegobroetto/observability-demo" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;Para rodar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sam build &lt;span class="nt"&gt;--use-container&lt;/span&gt;
sam deploy &lt;span class="nt"&gt;--config-env&lt;/span&gt; dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O README traz o passo a passo para reproduzir os três erros de propósito: reduzir o visibility timeout, remover a flag de resposta parcial e ver a DLQ recebendo mensagem.&lt;/p&gt;

&lt;p&gt;O projeto tem limitações que vale deixar claras. Não há testes automatizados, os alarmes cobrem só a fila de Estoque e as DLQs de Financeiro, da regra e do &lt;code&gt;processar_pedido&lt;/code&gt;, e os consumers simulam as integrações (estoque, e-mail, cobrança) em vez de chamar sistemas reais.&lt;/p&gt;




&lt;h2&gt;
  
  
  11.0 Conclusão
&lt;/h2&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxas6kinsdihmm6sbjxl7.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxas6kinsdihmm6sbjxl7.png" alt="Conclusao" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A pergunta "EventBridge ou SQS?" parte de uma premissa errada. O que importa é o que cada ponto do fluxo precisa: roteamento ou durabilidade. Às vezes é só um dos dois. No projeto de pedidos, com quatro consumidores, precisou dos dois.&lt;/p&gt;

&lt;p&gt;Configure DLQ no alvo da regra do EventBridge e em cada fila SQS desde o primeiro deploy, não depois que algo já sumiu. E antes de copiar um &lt;code&gt;BatchSize: 10&lt;/code&gt; de algum exemplo, faça duas contas: quanto tempo o lote leva no pior caso, e o que acontece quando a nona mensagem dá certo e a décima falha.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Diego Broetto é Tech Lead na Darede e escreve sobre AWS, serverless e infraestrutura como código.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Já passou por alguma dessas pegadinhas de um jeito diferente? Comenta aqui embaixo.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>eventbridge</category>
      <category>sqs</category>
    </item>
    <item>
      <title>AWS Lambda MicroVMs: Sandboxes Isolados com Controle Total do Ciclo de Vida</title>
      <dc:creator>Diego Broetto</dc:creator>
      <pubDate>Tue, 18 Aug 2026 15:55:37 +0000</pubDate>
      <link>https://dev.to/broettod/aws-lambda-microvms-sandboxes-isolados-com-controle-total-do-ciclo-de-vida-3kbc</link>
      <guid>https://dev.to/broettod/aws-lambda-microvms-sandboxes-isolados-com-controle-total-do-ciclo-de-vida-3kbc</guid>
      <description>&lt;p&gt;No dia 22/07/2026 a AWS anunciou o &lt;a href="https://aws.amazon.com/blogs/aws/run-isolated-sandboxes-with-full-lifecycle-control-aws-lambda-introduces-microvms/" rel="noopener noreferrer"&gt;AWS Lambda MicroVMs&lt;/a&gt;, um recurso novo dentro do Lambda, com API própria, separado das Functions que a gente já conhece. E não é só mais uma opção de runtime. É um primitivo de computação novo, pensado pra um problema que vinha incomodando bastante gente: como dar a cada usuário (ou sessão, ou agente de IA) um ambiente isolado de verdade, que suba rápido e mantenha estado, sem você ter que construir sua própria camada de virtualização do zero.&lt;/p&gt;

&lt;p&gt;Antes de ir pro exemplo prático vale entender por que isso existe. Se você constrói algo como um assistente de código com IA, um ambiente de execução interativo, uma plataforma de análise de dados ou um scanner de vulnerabilidades, você provavelmente já bateu de frente com esse trilema: VM tradicional isola bem mas demora minutos pra subir, container sobe rápido mas compartilha kernel com o host (o que exige um trabalho de hardening considerável pra rodar código não confiável), e Function as a Service é ótimo pra evento-resposta mas não guarda estado entre interações de uma sessão longa. O MicroVMs foi desenhado exatamente pra esse buraco no meio.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feqtprjw1s4yefqfl999s.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feqtprjw1s4yefqfl999s.png" alt=" " width="800" height="636"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Um aviso rápido antes de continuar: nos últimos artigos eu vim criando todos os templates utilizando o AWS SAM. Desta vez troquei pra Terraform, e vale explicar o porquê. SAM/CloudFormation continuam cobrindo bem o pedaço nativo de MicroVM Image e Network Connector. Isso não mudou. Mas o resto do laboratório (VPC, buckets, S3 Files, IAM) é onde eu realmente vivo no dia a dia, e o provider &lt;code&gt;hashicorp/aws&lt;/code&gt; (a partir da v6.40.0) já tem recursos nativos pra Amazon S3 Files (&lt;code&gt;aws_s3files_*&lt;/code&gt;). Pra imagem e o network connector, enquanto o provider não tem recurso nativo pra isso (&lt;a href="https://github.com/hashicorp/terraform-provider-aws/issues/48526" rel="noopener noreferrer"&gt;issue #48526&lt;/a&gt; ainda aberta), uso &lt;code&gt;null_resource&lt;/code&gt; + &lt;code&gt;local-exec&lt;/code&gt; como ponte pro AWS CLI. &lt;/p&gt;




&lt;h2&gt;
  
  
  1.0 O que muda em relação ao Lambda que você já usa
&lt;/h2&gt;

&lt;p&gt;O motor por baixo é o mesmo Firecracker que já roda as Functions, então o isolamento de VM (kernel próprio, sem recurso compartilhado entre sessões) é a mesma garantia de sempre. A diferença está no modelo de uso: em vez de deploy-then-invoke, aqui é &lt;strong&gt;imagem-então-lançamento&lt;/strong&gt;. Você empacota uma aplicação com Dockerfile, a AWS builda essa imagem, inicializa a aplicação dentro dela e tira um snapshot Firecracker da memória e do disco naquele estado pronto. Todo MicroVM lançado a partir dessa imagem depois disso não faz boot frio: ele resume direto do snapshot.&lt;/p&gt;

&lt;p&gt;Na prática isso significa que sua API já está de pé no instante em que o lançamento termina. E tem outra coisa que o Lambda tradicional não te dá: controle explícito de ciclo de vida. Você define uma política de idle (quanto tempo sem tráfego até suspender, quanto tempo suspenso antes de encerrar de vez, se retoma automaticamente ou não), e o MicroVM pode ficar suspenso com estado intacto por até 8 horas de runtime total. Por padrão, o container roda com um conjunto restrito de capabilities do Linux, e é isso que vai importar no exemplo a seguir.&lt;/p&gt;




&lt;h2&gt;
  
  
  2.0 Colocando isso pra rodar
&lt;/h2&gt;

&lt;p&gt;Montei uma API de workspace de arquivos com FastAPI, com o objetivo de: provar que o estado sobrevive não só a uma suspensão, mas ao fim completo do MicroVM. O disco não é local, que é parte do snapshot e some quando o MicroVM é terminado. É o &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-files-mounting-lambda.html" rel="noopener noreferrer"&gt;Amazon S3 Files&lt;/a&gt; montado dentro da imagem, um sistema de arquivos que expõe objetos do S3 como se fossem arquivos comuns, via NFS, no mesmo caminho local. O workspace passa a viver fora do ciclo de vida de qualquer MicroVM individual, e cada arquivo escrito vira um objeto de verdade no bucket por trás.&lt;/p&gt;

&lt;p&gt;O repositório completo está em &lt;a href="https://github.com/diegobroetto/lambda-microvms-workspace-demo" rel="noopener noreferrer"&gt;&lt;code&gt;lambda-microvms-workspace-demo&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.1 A API: app.py
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;aws_lambda_powertools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Logger&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;UploadFile&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi.responses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FileResponse&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Sem inject_lambda_context aqui: esse decorator injeta cold_start,
# function_request_id etc. a partir do objeto context de uma invocação
# Lambda, e esse FastAPI roda como servidor de longa duração dentro do
# MicroVM, sem um context por request. O que eu ganho de graça mesmo
# assim é o log em JSON estruturado, sem escrever formatter na mão.
&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;workspace-microvm&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Esse caminho é o ponto de montagem do Amazon S3 Files, não disco
# local do MicroVM. Cada arquivo aqui é, por baixo, um objeto no S3,
# então sobrevive à suspensão E à terminação do MicroVM.
&lt;/span&gt;&lt;span class="n"&gt;WORKSPACE_DIR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/mnt/workspace&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/health&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;health&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;mounted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WORKSPACE_DIR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;WORKSPACE_DIR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_mount&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;workspace_mounted&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mounted&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/files&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;upload_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UploadFile&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;destination&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WORKSPACE_DIR&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Arquivo salvo via S3 Files&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size_bytes&lt;/span&gt;&lt;span class="o"&gt;=&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;content&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;filename&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;size_bytes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&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;content&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stored_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;


&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/files&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;list_files&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;filename&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;size_bytes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;st_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;modified_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ctime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;st_mtime&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;WORKSPACE_DIR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;iterdir&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_file&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Listagem de workspace solicitada&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total_arquivos&lt;/span&gt;&lt;span class="o"&gt;=&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;files&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;workspace_dir&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WORKSPACE_DIR&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;files&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/files/{filename}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;download_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;file_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WORKSPACE_DIR&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Download solicitado para arquivo inexistente&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;detail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Arquivo não encontrado no workspace&lt;/span&gt;&lt;span class="sh"&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;FileResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2.2 Montando o S3 Files: entrypoint.sh
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/sh&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;

&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /mnt/workspace

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;S3FILES_MOUNT_TARGET&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&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;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"ERRO: variável S3FILES_MOUNT_TARGET não definida."&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# S3FILES_MOUNT_TARGET chega aqui como variável de ambiente definida&lt;/span&gt;
&lt;span class="c"&gt;# na CRIAÇÃO DA IMAGEM, não no lançamento. Mais sobre esse detalhe&lt;/span&gt;
&lt;span class="c"&gt;# na seção 2.4.&lt;/span&gt;
mount &lt;span class="nt"&gt;-t&lt;/span&gt; nfs4 &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;nfsvers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4.1,rsize&lt;span class="o"&gt;=&lt;/span&gt;1048576,wsize&lt;span class="o"&gt;=&lt;/span&gt;1048576,hard,timeo&lt;span class="o"&gt;=&lt;/span&gt;600,retrans&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;S3FILES_MOUNT_TARGET&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:/lambda"&lt;/span&gt; /mnt/workspace

&lt;span class="nb"&gt;exec &lt;/span&gt;uvicorn app:app &lt;span class="nt"&gt;--host&lt;/span&gt; 0.0.0.0 &lt;span class="nt"&gt;--port&lt;/span&gt; 5000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2.3 Estrutura do repositório: da imagem ao Terraform
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lambda-microvms-workspace-demo/
├── app/                    # FastAPI + entrypoint.sh + Dockerfile
├── infra/                  # Terraform "flat": vpc, s3, s3files, iam, microvm, outputs
├── scripts/                # só helpers chamados pelo apply/destroy (create_*, delete_*)
├── docs/
│   └── MANUAL.md           # guia manual passo a passo pra lançar, testar e provar durabilidade
└── Makefile                # só init / plan / apply / destroy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reparem numa coisa, o repositório não tem &lt;code&gt;make run&lt;/code&gt;, &lt;code&gt;make test&lt;/code&gt; nem &lt;code&gt;make proof&lt;/code&gt;. Isso é proposital. O &lt;code&gt;Makefile&lt;/code&gt; só cobre &lt;code&gt;init&lt;/code&gt;, &lt;code&gt;plan&lt;/code&gt;, &lt;code&gt;apply&lt;/code&gt; e &lt;code&gt;destroy&lt;/code&gt;: tudo que é infraestrutura versionada. Lançar um MicroVM, gerar token, testar com curl e reproduzir a prova de durabilidade viraram uma sequência de comandos documentada em &lt;code&gt;docs/MANUAL.md&lt;/code&gt;, não um script automatizado. O motivo é o mesmo que já defendi antes: imagem é infraestrutura versionada, MicroVM em execução é sessão efêmera, e sessão efêmera não deveria ter um &lt;code&gt;make&lt;/code&gt; escondendo o que está de fato acontecendo, e muito menos ir parar no state do Terraform.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;"Sempre que trabalho com Makefiles, me vem a memória do tempo da graduação, quando criava os arquivos de build para rodar meus programas em C."&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  2.4 Provisionando com Terraform
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;infra/&lt;/code&gt; fica flat de propósito: &lt;code&gt;vpc.tf&lt;/code&gt;, &lt;code&gt;s3.tf&lt;/code&gt;, &lt;code&gt;s3files.tf&lt;/code&gt;, &lt;code&gt;iam.tf&lt;/code&gt;, &lt;code&gt;microvm.tf&lt;/code&gt;, &lt;code&gt;outputs.tf&lt;/code&gt;. A VPC é mínima, sem NAT gateway: nem o MicroVM nem os mount targets do S3 Files precisam de saída pra internet, só de rota entre si na porta 2049.&lt;/p&gt;

&lt;p&gt;Uma peça de IAM que não tinha aparecido antes: o S3 Files usa um papel próprio pra sincronizar o que é escrito via NFS de volta pro bucket, e esse papel assume o principal &lt;code&gt;elasticfilesystem.amazonaws.com&lt;/code&gt;. O S3 Files compartilha parte da arquitetura de serviço com o EFS por baixo, o que explica o principal emprestado. Esse papel de sync precisa de &lt;code&gt;s3:GetObject&lt;/code&gt;, &lt;code&gt;s3:PutObject&lt;/code&gt;, &lt;code&gt;s3:ListBucket&lt;/code&gt; e &lt;code&gt;s3:DeleteObject&lt;/code&gt; no bucket do workspace, e o bucket &lt;strong&gt;precisa ter versionamento habilitado&lt;/strong&gt;. Sem isso a sincronização não funciona.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# infra/iam.tf (trecho)&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_role"&lt;/span&gt; &lt;span class="s2"&gt;"s3files_sync"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.project_name}-s3files-sync-role"&lt;/span&gt;

  &lt;span class="nx"&gt;assume_role_policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;Version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;
    &lt;span class="nx"&gt;Statement&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
      &lt;span class="nx"&gt;Effect&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;
      &lt;span class="nx"&gt;Principal&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Service&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"elasticfilesystem.amazonaws.com"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;Action&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"sts:AssumeRole"&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;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_role_policy"&lt;/span&gt; &lt;span class="s2"&gt;"s3files_sync_bucket_access"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.project_name}-s3files-sync-bucket-access"&lt;/span&gt;
  &lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s3files_sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="nx"&gt;policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;Version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;
    &lt;span class="nx"&gt;Statement&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
      &lt;span class="nx"&gt;Sid&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"SyncWorkspaceBucket"&lt;/span&gt;
      &lt;span class="nx"&gt;Effect&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;
      &lt;span class="nx"&gt;Action&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"s3:PutObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"s3:ListBucket"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"s3:DeleteObject"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="nx"&gt;Resource&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workspace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"${aws_s3_bucket.workspace.arn}/*"&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;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;microvm.tf&lt;/code&gt; é onde fica a ponte via CLI, porque o provider ainda não tem &lt;code&gt;aws_lambda_microvm_image&lt;/code&gt; nem equivalente pro network connector. O detalhe que mudou desde a última vez: o &lt;code&gt;S3FILES_MOUNT_TARGET&lt;/code&gt; entra como variável de ambiente na &lt;strong&gt;criação da imagem&lt;/strong&gt;, não no lançamento. Variáveis de ambiente do MicroVM são resolvidas em build time e congelam no snapshot. Se eu passasse isso no &lt;code&gt;run-microvm&lt;/code&gt;, o &lt;code&gt;entrypoint.sh&lt;/code&gt; simplesmente não veria o valor, porque ele já rodou antes de qualquer parâmetro de lançamento existir.&lt;/p&gt;

&lt;p&gt;Outro ajuste: o provider do Terraform não expõe o DNS do mount target do S3 Files (diferente do EFS, que tem um DNS previsível por AZ). O jeito que encontrei foi ler o IP privado da ENI do mount target direto do Terraform e passar esse IP como valor da variável de ambiente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# infra/microvm.tf (trecho)&lt;/span&gt;
&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_network_interface"&lt;/span&gt; &lt;span class="s2"&gt;"s3files_mount_target"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"description"&lt;/span&gt;
    &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"EFS mount target for fs-*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# o S3 Files reaproveita o padrão de descrição do EFS na ENI&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"subnet-id"&lt;/span&gt;
    &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;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;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"null_resource"&lt;/span&gt; &lt;span class="s2"&gt;"microvm_image"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;triggers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;artifact_etag&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_s3_object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app_artifact&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;etag&lt;/span&gt;
    &lt;span class="nx"&gt;mount_ip&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_network_interface&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s3files_mount_target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private_ip&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;provisioner&lt;/span&gt; &lt;span class="s2"&gt;"local-exec"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${path.module}/../scripts/create_microvm_image.sh"&lt;/span&gt;
    &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;IMAGE_NAME&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_name&lt;/span&gt;
      &lt;span class="nx"&gt;BASE_IMAGE_ARN&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"arn:aws:lambda:${var.aws_region}:aws:microvm-image:al2023-1"&lt;/span&gt;
      &lt;span class="nx"&gt;BUILD_ROLE_ARN&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;microvm_build&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
      &lt;span class="nx"&gt;ARTIFACT_URI&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"s3://${aws_s3_bucket.artifact.id}/${aws_s3_object.app_artifact.key}"&lt;/span&gt;
      &lt;span class="nx"&gt;MOUNT_TARGET_IP&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_network_interface&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s3files_mount_target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private_ip&lt;/span&gt;
      &lt;span class="nx"&gt;MIN_MEMORY_MIB&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;microvm_memory_mb&lt;/span&gt;
      &lt;span class="nx"&gt;AWS_REGION&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_region&lt;/span&gt;
      &lt;span class="nx"&gt;OUTPUT_FILE&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${path.module}/../.build/image_arn.txt"&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;O &lt;code&gt;scripts/create_microvm_image.sh&lt;/code&gt; monta o comando de fato:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws lambda-microvms create-microvm-image &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;IMAGE_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--base-image-arn&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BASE_IMAGE_ARN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--build-role-arn&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BUILD_ROLE_ARN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--code-artifact&lt;/span&gt; &lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;uri&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ARTIFACT_URI&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--additional-os-capabilities&lt;/span&gt; &lt;span class="s1"&gt;'["ALL"]'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--environment-variables&lt;/span&gt; &lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;S3FILES_MOUNT_TARGET&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MOUNT_TARGET_IP&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resources&lt;/span&gt; &lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;minimumMemoryInMiB&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MIN_MEMORY_MIB&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reparei que a memória se divide em dois lugares diferentes do ciclo de vida: &lt;code&gt;minimumMemoryInMiB&lt;/code&gt; na criação da imagem define o piso (o mesmo piso de 512 MB que o S3 Files exige pra habilitar leitura direta do S3), e a política de idle, que é por lançamento, só aparece depois, no &lt;code&gt;run-microvm&lt;/code&gt;. Faz sentido: memória é característica da imagem (do jeito que a aplicação foi construída e testada), idle é característica de cada sessão.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;scripts/&lt;/code&gt; ficou só com os helpers que o Terraform chama (&lt;code&gt;create_microvm_image.sh&lt;/code&gt;, &lt;code&gt;create_network_connector.sh&lt;/code&gt;) e os de limpeza (&lt;code&gt;delete_microvm_image.sh&lt;/code&gt;, &lt;code&gt;delete_network_connector.sh&lt;/code&gt;, chamados no &lt;code&gt;terraform destroy&lt;/code&gt;). Nada de script de teste ali. Isso é o próximo passo, na mão.&lt;/p&gt;

&lt;p&gt;Pra provisionar tudo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make init &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; make apply
&lt;span class="c"&gt;# equivalente a:&lt;/span&gt;
&lt;span class="c"&gt;# cd infra &amp;amp;&amp;amp; terraform init &amp;amp;&amp;amp; terraform apply&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2.5 Rodando na mão: do lançamento ao teste
&lt;/h3&gt;

&lt;p&gt;O &lt;code&gt;docs/MANUAL.md&lt;/code&gt; documenta essa sequência inteira. Primeiro, pego os outputs do Terraform e lanço o MicroVM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;infra
&lt;span class="nv"&gt;IMAGE_ARN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;terraform output &lt;span class="nt"&gt;-raw&lt;/span&gt; microvm_image_arn&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;EXECUTION_ROLE_ARN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;terraform output &lt;span class="nt"&gt;-raw&lt;/span&gt; microvm_execution_role_arn&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;CONNECTOR_ARN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;terraform output &lt;span class="nt"&gt;-raw&lt;/span&gt; network_connector_arn&lt;span class="si"&gt;)&lt;/span&gt;

aws lambda-microvms run-microvm &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--image-identifier&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;IMAGE_ARN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--image-version&lt;/span&gt; &lt;span class="s2"&gt;"1.0"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--execution-role-arn&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;EXECUTION_ROLE_ARN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--egress-network-connectors&lt;/span&gt; &lt;span class="s2"&gt;"[&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CONNECTOR_ARN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;]"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--idle-policy&lt;/span&gt; &lt;span class="s1"&gt;'{"maxIdleDurationSeconds":300,"suspendedDurationSeconds":1800,"autoResumeEnabled":true}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sem variável de ambiente aqui: ela já foi resolvida lá atrás, na imagem. O comando devolve &lt;code&gt;microvmId&lt;/code&gt; e &lt;code&gt;endpointUrl&lt;/code&gt;. Com isso, gero um token de curta duração:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;aws lambda-microvms create-microvm-auth-token &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--microvm-identifier&lt;/span&gt; &amp;lt;microvmId&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--expiration-in-minutes&lt;/span&gt; 30 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--allowed-ports&lt;/span&gt; &lt;span class="s1"&gt;'[{"port":5000}]'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'authToken."X-aws-proxy-auth"'&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; text&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0hkdg6fhytzq2f8096fv.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0hkdg6fhytzq2f8096fv.png" alt=" " width="800" height="245"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;E testo o fluxo básico:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"&amp;lt;endpointUrl&amp;gt;/health"&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-aws-proxy-auth: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TOKEN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"&amp;lt;endpointUrl&amp;gt;/files"&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-aws-proxy-auth: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TOKEN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"file=@notas.txt"&lt;/span&gt;

curl &lt;span class="s2"&gt;"&amp;lt;endpointUrl&amp;gt;/files"&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-aws-proxy-auth: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TOKEN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fds5bvyc8i8bf3pxvz0nl.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fds5bvyc8i8bf3pxvz0nl.png" alt=" " width="799" height="588"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2.6 Provando a durabilidade: terminate e relança
&lt;/h3&gt;

&lt;p&gt;Até aqui, nada muito diferente do disco local. A prova de verdade vem agora: em vez de deixar o MicroVM só suspender, eu &lt;strong&gt;termino ele por completo&lt;/strong&gt; e lanço um MicroVM novo, do zero, a partir da mesma imagem.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws lambda-microvms terminate-microvm &lt;span class="nt"&gt;--microvm-identifier&lt;/span&gt; &amp;lt;microvmId-original&amp;gt;

&lt;span class="c"&gt;# lança um MicroVM completamente novo, com um microvmId diferente&lt;/span&gt;
aws lambda-microvms run-microvm &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--image-identifier&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;IMAGE_ARN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--image-version&lt;/span&gt; &lt;span class="s2"&gt;"1.0"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--execution-role-arn&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;EXECUTION_ROLE_ARN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--egress-network-connectors&lt;/span&gt; &lt;span class="s2"&gt;"[&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CONNECTOR_ARN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;]"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--idle-policy&lt;/span&gt; &lt;span class="s1"&gt;'{"maxIdleDurationSeconds":300,"suspendedDurationSeconds":1800,"autoResumeEnabled":true}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Gero um token novo pro &lt;code&gt;microvmId&lt;/code&gt; novo, e chamo &lt;code&gt;GET /files&lt;/code&gt; de novo:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7w66hybedqz8th3ihpuy.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7w66hybedqz8th3ihpuy.png" alt=" " width="800" height="654"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;notas.txt&lt;/code&gt; aparece no &lt;code&gt;GET /files&lt;/code&gt; do MicroVM novo, com um &lt;code&gt;microvmId&lt;/code&gt; que nunca existiu antes. O snapshot desse MicroVM não tinha nenhuma referência a esse arquivo, porque o arquivo nunca esteve no disco local pra começo de conversa. Ele sempre foi um objeto no bucket S3 por trás, só sendo lido através do mount do S3 Files.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.7 Limpando
&lt;/h3&gt;

&lt;p&gt;Antes do &lt;code&gt;terraform destroy&lt;/code&gt;, preciso terminar qualquer MicroVM ativo na mão:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws lambda-microvms terminate-microvm &lt;span class="nt"&gt;--microvm-identifier&lt;/span&gt; &amp;lt;microvmId-atual&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O motivo é de rede, não de custo: um MicroVM em execução mantém uma interface de rede anexada às subnets privadas via egress network connector, e o destroy da VPC falha enquanto essa ENI ainda existir. Só depois disso:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make destroy
&lt;span class="c"&gt;# equivalente a:&lt;/span&gt;
&lt;span class="c"&gt;# cd infra &amp;amp;&amp;amp; terraform destroy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3.0 O que eu levo em conta ao usar isso
&lt;/h2&gt;

&lt;h3&gt;
  
  
  3.1 Durabilidade e estado
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;S3 Files resolve um problema que nem suspensão longa nem export manual resolvem bem sozinhos.&lt;/strong&gt; Disco local sobrevive suspensão mas está preso ao teto de 8 horas e ao ciclo de vida daquele MicroVM específico. Escrever direto no bucket via boto3 funciona, mas exige um passo explícito de upload que é fácil de esquecer. Montar S3 Files tira essa escolha do meio: a escrita já cai como objeto no bucket, sem passo extra, e ainda por cima falando com o S3, que é o storage que eu já confio.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;512 MB de memória é o corte pra leitura direta do S3, e agora isso vive na imagem, não no lançamento.&lt;/strong&gt; Abaixo desse valor o mount ainda funciona, mas sem o caminho otimizado de leitura direta do S3 Files. Definir &lt;code&gt;minimumMemoryInMiB&lt;/code&gt; na criação da imagem em vez de deixar solto no &lt;code&gt;run-microvm&lt;/code&gt; evita que alguém lance um MicroVM subdimensionado sem perceber.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.2 Segurança e acesso
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;A capability de montagem é opt-in por um motivo, e eu penso duas vezes antes de habilitar.&lt;/strong&gt; &lt;code&gt;--additional-os-capabilities '["ALL"]'&lt;/code&gt; amplia bastante o que o container consegue fazer dentro do MicroVM, não é só liberação pontual pra mount. Vale pesar isso contra o quanto de isolamento eu realmente preciso pro caso de uso. Pra rodar código gerado por IA de origem desconhecida, por exemplo, eu penso duas vezes antes de dar essa capability extra.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Permissão de S3 Files é separada de permissão de S3 comum, e agora percebi que são três papéis, não dois.&lt;/strong&gt; &lt;code&gt;s3files:ClientMount&lt;/code&gt; e &lt;code&gt;s3files:ClientWrite&lt;/code&gt; na role de execução do MicroVM cobrem o acesso via NFS. Mas o próprio S3 Files também precisa de uma role de sync (assumindo &lt;code&gt;elasticfilesystem.amazonaws.com&lt;/code&gt;) pra escrever de volta no bucket, com versionamento obrigatório. Três papéis diferentes fazendo três coisas diferentes, fácil de confundir se você já tem uma policy de S3 pronta e assume que ela cobre tudo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rede não é implícita, é uma peça que eu declaro.&lt;/strong&gt; Sem o egress network connector apontando pra minha VPC, o MicroVM simplesmente não alcança o mount target do S3 Files. Isso é bom: rede que só existe porque eu declarei explicitamente é mais fácil de auditar do que rede que "só funciona" por padrão.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.3 Padrões de implementação
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Variável de ambiente pro mount vai na imagem, não no lançamento, e isso é fácil de errar uma vez.&lt;/strong&gt; Da primeira vez que tentei passar &lt;code&gt;S3FILES_MOUNT_TARGET&lt;/code&gt; no &lt;code&gt;run-microvm&lt;/code&gt;, o &lt;code&gt;entrypoint.sh&lt;/code&gt; simplesmente não via o valor. Variáveis de ambiente do MicroVM são resolvidas em build time, junto com o resto do snapshot. Isso é consistente com a lição do post anterior sobre estado congelado, só que na direção contrária: aqui eu &lt;em&gt;preciso&lt;/em&gt; que congele, porque é informação de infraestrutura, não de sessão.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;O provider do Terraform não expõe DNS do mount target do S3 Files, só a ENI.&lt;/strong&gt; Diferente do EFS, que tem um endpoint DNS previsível, tive que resolver o IP privado da interface de rede via &lt;code&gt;data "aws_network_interface"&lt;/code&gt; filtrando pela descrição e pela subnet. Funciona, mas é um detalhe frágil: se a AWS mudar o padrão de descrição da ENI, esse filtro quebra.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Montar em runtime (dentro do container), não em build time (na instrução &lt;code&gt;RUN&lt;/code&gt; do Dockerfile), continua valendo.&lt;/strong&gt; O mount em si ainda acontece no &lt;code&gt;entrypoint.sh&lt;/code&gt; quando o container sobe, porque o mount target só é alcançável quando a rede da imagem já está configurada. A variável de ambiente pode congelar no build, a chamada de &lt;code&gt;mount&lt;/code&gt; não pode.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Múltiplos MicroVMs podem montar o mesmo file system ao mesmo tempo, e isso muda o desenho.&lt;/strong&gt; Diferente do disco local, que é exclusivo daquele MicroVM, o S3 Files permite compartilhar workspace entre sessões diferentes se fizer sentido pro seu caso. Vale filtrar por sessão dentro da aplicação (um prefixo de diretório por usuário, por exemplo) pra não misturar dado de gente diferente sem querer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NFS tem custo de latência que disco local não tem.&lt;/strong&gt; Toda leitura e escrita passa pela rede até o mount target. Pra arquivo pequeno e acesso frequente isso raramente é perceptível, mas pra workload de I/O intenso vale medir antes de assumir que dá na mesma.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.4 Observabilidade
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Powertools funciona em MicroVMs, mas nem todo pedaço faz sentido do mesmo jeito.&lt;/strong&gt; O &lt;code&gt;Logger&lt;/code&gt; estruturado em JSON é ganho direto, sem precisar escrever formatter na mão. Mas &lt;code&gt;inject_lambda_context&lt;/code&gt;, que injeta &lt;code&gt;cold_start&lt;/code&gt; e &lt;code&gt;function_request_id&lt;/code&gt; automaticamente, foi desenhado pra um &lt;code&gt;lambda_handler(event, context)&lt;/code&gt; que roda uma vez por invocação. Aqui é um servidor FastAPI de longa duração, sem um &lt;code&gt;context&lt;/code&gt; novo a cada request, então esse pedaço específico eu deixei de fora. Vale essa checagem sempre que trouxer uma lib pensada pra Function pra dentro de um MicroVM: parte do contrato pode não existir mais.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.5 Arquitetura e deploy
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Terraform e SAM/CloudFormation não competem aqui, cada um cobre uma parte diferente do mesmo problema.&lt;/strong&gt; SAM tem recurso nativo pra &lt;code&gt;AWS::Lambda::MicrovmImage&lt;/code&gt; desde o lançamento, o provider Terraform da AWS, não. Daí o &lt;code&gt;null_resource&lt;/code&gt; + &lt;code&gt;local-exec&lt;/code&gt; como ponte enquanto o &lt;a href="https://github.com/hashicorp/terraform-provider-aws/issues/48526" rel="noopener noreferrer"&gt;issue #48526&lt;/a&gt; não fecha. Em compensação, o Terraform já tem recursos nativos pra S3 Files (&lt;code&gt;aws_s3files_*&lt;/code&gt;) desde a v6.40.0, o que deixa o resto do laboratório mais direto de escrever do que ficaria em CloudFormation puro. Escolher a stack de IaC também é escolher onde você aceita workaround (palavra chique para gambiarra).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Imagem entra no pipeline de IaC, lançamento não, e isso ficou ainda mais explícito nessa versão do repositório.&lt;/strong&gt; O &lt;code&gt;Makefile&lt;/code&gt; só tem &lt;code&gt;init&lt;/code&gt;, &lt;code&gt;plan&lt;/code&gt;, &lt;code&gt;apply&lt;/code&gt; e &lt;code&gt;destroy&lt;/code&gt;, de propósito, sem &lt;code&gt;run&lt;/code&gt;, sem &lt;code&gt;test&lt;/code&gt;. Lançar um MicroVM é decisão de runtime, documentada em &lt;code&gt;docs/MANUAL.md&lt;/code&gt; como sequência manual de comandos, não escondida atrás de um alvo de Makefile que faria parecer parte do provisionamento.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MicroVMs e Functions não competem, se complementam.&lt;/strong&gt; O próprio anúncio da AWS deixa isso explícito: Functions seguem sendo a escolha certa pro backbone evento-resposta da aplicação, e MicroVMs entra especificamente na etapa que precisa rodar código não confiável (gerado por usuário ou por IA) isolado, com estado que precisa sobreviver além de uma única execução.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.6 Limites honestos deste laboratório
&lt;/h3&gt;

&lt;p&gt;Não tem CI rodando isso. Os nomes de alguns parâmetros de CLI e os principals de trust policy (&lt;code&gt;elasticfilesystem.amazonaws.com&lt;/code&gt; pro papel de sync, por exemplo) eu confirmei o suficiente pra funcionar no meu teste, mas não bati contra a referência oficial linha por linha. Vale conferir antes de replicar em produção. A criação de imagem não é totalmente idempotente: rodar &lt;code&gt;create_microvm_image.sh&lt;/code&gt; duas vezes cria uma versão nova em vez de atualizar in-place. E a ponte via &lt;code&gt;null_resource&lt;/code&gt; + &lt;code&gt;local-exec&lt;/code&gt; pro image/connector é workaround assumido, não o caminho idiomático. Se seu time prioriza Terraform 100% nativo pra esse pedaço específico, SAM ainda é a opção mais direta hoje.&lt;/p&gt;




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

&lt;p&gt;O que mais me chamou atenção nesse anúncio foi perceber que a AWS não criou uma tecnologia nova, ela deu acesso direto a uma peça que já sustentava o Lambda por trás dos panos há anos. Isso é coerente quando se entende a forma que o Firecracker funciona: entender a engenharia por baixo ajuda a prever pra onde o produto vai. E o Powertools que usei aqui pro Logger é o mesmo que detalhei &lt;a href="https://dev.to/broettod/observabilidade-serverless-sem-surpresa-no-bolso-logs-metricas-e-traces-com-cloudwatch-x-ray-do-1hb9"&gt;naquele post sobre observabilidade serverless&lt;/a&gt;. O Logger continua valendo em MicroVM, só o contrato de invocação que muda. Trocar SAM por Terraform pra montar esse laboratório não mudou a história que o artigo conta, só me obrigou a ser mais explícito sobre o que cada stack cobre nativamente e onde ainda falta peça. Pensei em testar isso e ver até onde dá pra empurrar: rodar um agente de IA gerando e executando código de verdade dentro de um MicroVM com esse mesmo workspace compartilhado via S3 Files, e medir o custo real de manter várias sessões vivas ao mesmo tempo, isso pode gerar um novo artigo pra compartilhar.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>lambda</category>
      <category>serverless</category>
      <category>firecracker</category>
    </item>
    <item>
      <title>Observabilidade serverless sem surpresa no bolso: logs, métricas e traces com CloudWatch + X-Ray do jeito certo</title>
      <dc:creator>Diego Broetto</dc:creator>
      <pubDate>Thu, 16 Jul 2026 19:50:43 +0000</pubDate>
      <link>https://dev.to/broettod/observabilidade-serverless-sem-surpresa-no-bolso-logs-metricas-e-traces-com-cloudwatch-x-ray-do-1hb9</link>
      <guid>https://dev.to/broettod/observabilidade-serverless-sem-surpresa-no-bolso-logs-metricas-e-traces-com-cloudwatch-x-ray-do-1hb9</guid>
      <description>&lt;p&gt;Tem uma fatura de CloudWatch que eu nunca vou esquecer.&lt;/p&gt;

&lt;p&gt;Uma função Lambda simples, rodando há algumas semanas em produção. Sem grande volume. Sem nada de especial. Só que alguém tinha esquecido de configurar retenção de log, o X-Ray estava com sampling em 100%, e o log level estava em DEBUG porque "vou ajustar depois". A conta veio quase três vezes o esperado.&lt;/p&gt;

&lt;p&gt;O pior não foi o valor. Foi saber que meu cliente tinha pagado para guardar lixo. Stack trace de erro que já estava resolvido. Trace de requisição que durou 12ms e nunca mais ninguém ia abrir. Log de evento completo com payload de cliente em texto plano num log group que ia existir pra sempre.&lt;/p&gt;

&lt;p&gt;Observabilidade serverless não é caro por natureza. Fica caro quando você liga tudo no máximo e esquece. Este artigo é sobre não esquecer.&lt;/p&gt;




&lt;h2&gt;
  
  
  1.0 O problema específico do serverless
&lt;/h2&gt;

&lt;p&gt;Em servidores tradicionais, você tem SSH. Tem processo rodando. Tem &lt;code&gt;top&lt;/code&gt;, &lt;code&gt;htop&lt;/code&gt;, &lt;code&gt;journalctl&lt;/code&gt;. Quando algo dá errado, você entra na máquina e olha.&lt;/p&gt;

&lt;p&gt;No Lambda, não tem nada disso. A função executa em milissegundos, o container some, e você ficou com o que colocou no log. Se não colocou nada útil, ou colocou tudo de forma errada, você está debugando no escuro.&lt;/p&gt;

&lt;p&gt;Aí vem o impulso natural: loga tudo. Ativa X-Ray em todas as funções. Cria métrica para cada coisa que se move. Funciona. Mas o preço aparece no fim do mês.&lt;/p&gt;

&lt;p&gt;A questão não é observar menos. É observar o que importa.&lt;/p&gt;




&lt;h2&gt;
  
  
  2.0 Os três pilares e o que cada um custa de verdade
&lt;/h2&gt;

&lt;p&gt;Antes de falar de implementação, vale entender a tabela de preços do que você está usando.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CloudWatch Logs&lt;/strong&gt; cobra em três momentos distintos: ingestão ($0,50/GB), armazenamento ($0,03/GB/mês) e consultas no Logs Insights ($0,005/GB escaneado). O armazenamento parece barato até você calcular que o padrão de retenção é "nunca expirar". Um log group de função Lambda com volume médio, sem retenção configurada, acumula sem parar.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CloudWatch Metrics&lt;/strong&gt; tem métricas padrão do Lambda gratuitas. São muitas e cobrem bem o essencial: &lt;code&gt;Duration&lt;/code&gt;, &lt;code&gt;Errors&lt;/code&gt;, &lt;code&gt;Throttles&lt;/code&gt;, &lt;code&gt;ConcurrentExecutions&lt;/code&gt;, &lt;code&gt;IteratorAge&lt;/code&gt; para Streams. O custo começa quando você cria métricas customizadas: $0,30 por métrica por mês. Soa pouco. Mas se você criar uma métrica por &lt;code&gt;tenant_id&lt;/code&gt; com 500 tenants, são $150/mês só de métricas, sem incluir as chamadas de API para publicá-las.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;X-Ray&lt;/strong&gt; cobra $5,00 por 1 milhão de traces registrados e $0,50 por 1 milhão consultados. O sampling padrão da AWS é 1 requisição por segundo mais 5% do restante. Para a maioria das cargas, está OK. Para uma função processando 10 mil req/seg, 5% são 500 traces por segundo, 43 milhões por mês, $215 só de traces.&lt;/p&gt;

&lt;p&gt;Esses números não são argumentos contra usar essas ferramentas. São argumentos para configurá-las de forma intencional.&lt;/p&gt;




&lt;h2&gt;
  
  
  3.0 Logs: estrutura antes de volume
&lt;/h2&gt;

&lt;p&gt;O problema mais comum que vejo não é o volume de logs. É o formato.&lt;/p&gt;

&lt;p&gt;Log em texto livre não serve em escala. Você não consegue filtrar, não consegue agregar, não consegue criar métricas a partir dele. Funciona para debug manual no console, mas quebra quando você precisa entender o que aconteceu às 3h da manhã em 200 invocações simultâneas.&lt;/p&gt;

&lt;p&gt;JSON é o formato certo. O Lambda suporta nativamente desde 2023:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Globals&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Function&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;LoggingConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;LogFormat&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;JSON&lt;/span&gt;
      &lt;span class="na"&gt;ApplicationLogLevel&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;INFO&lt;/span&gt;
      &lt;span class="na"&gt;SystemLogLevel&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;WARN&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Com isso, cada log já sai estruturado com &lt;code&gt;timestamp&lt;/code&gt;, &lt;code&gt;level&lt;/code&gt;, &lt;code&gt;requestId&lt;/code&gt; e a mensagem. Você ainda precisa estruturar o que coloca no log, mas o envelope já está pronto.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.1 Powertools Logger: o que ele faz que você não quer fazer na mão
&lt;/h3&gt;

&lt;p&gt;O AWS Lambda Powertools tem um &lt;code&gt;Logger&lt;/code&gt; que, além de JSON, injeta contexto da invocação automaticamente:&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;aws_lambda_powertools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Logger&lt;/span&gt;

&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pedidos&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@logger.inject_lambda_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log_event&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Pedido recebido&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pedido_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;valor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;valor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O que aparece no log:&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;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"INFO"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Pedido recebido"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pedido_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"abc-123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"valor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;179.80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cold_start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"function_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;"pedidos-criar-prod"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"function_memory_size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"function_arn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:lambda:..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"function_request_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-07-14T10:23:41.123Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pedidos"&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;Exemplo no console:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqiebsavu8pz8asi5603g.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqiebsavu8pz8asi5603g.webp" alt=" " width="800" height="266"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cold_start&lt;/code&gt; está lá de graça. &lt;code&gt;function_request_id&lt;/code&gt; está lá. Tudo que você precisaria escrever na mão já vem junto.&lt;/p&gt;

&lt;p&gt;Um detalhe importante: &lt;code&gt;log_event=False&lt;/code&gt;. Com &lt;code&gt;True&lt;/code&gt;, o payload completo do evento vai para o log. Útil em desenvolvimento. Em produção, isso significa que dados do cliente vão para o CloudWatch em texto plano, e você vai pagar para armazenar informação que provavelmente não deveria estar lá. Controle isso por variável de ambiente:&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="nd"&gt;@logger.inject_lambda_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log_event&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LOG_EVENT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No template SAM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;LOG_EVENT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!If&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;IsDev&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3.2 append_keys: propagando contexto sem repetição
&lt;/h3&gt;

&lt;p&gt;Dentro do handler, você frequentemente quer que o &lt;code&gt;pedido_id&lt;/code&gt; apareça em todos os logs daquela invocação, não só no primeiro. O &lt;code&gt;append_keys&lt;/code&gt; faz isso:&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;pedido_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pathParameters&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pedido_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append_keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pedido_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;pedido_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Daqui pra frente, todo log inclui pedido_id automaticamente
&lt;/span&gt;    &lt;span class="n"&gt;pedido&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;buscar_pedido&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pedido_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Pedido encontrado&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;pedido&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3.3 Retenção: configure no primeiro deploy, não depois
&lt;/h3&gt;

&lt;p&gt;O padrão do Lambda é criar o log group automaticamente com retenção "Nunca expirar". Isso parece inofensivo até você perceber que é um bug, não uma feature.&lt;/p&gt;

&lt;p&gt;Configure via CloudFormation/SAM para não depender de lembrar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;PedidosLogGroup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Logs::LogGroup&lt;/span&gt;
  &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;LogGroupName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Sub&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/aws/lambda/pedidos-criar-${Environment}"&lt;/span&gt;
    &lt;span class="na"&gt;RetentionInDays&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;LogRetentionDays&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Com um parâmetro &lt;code&gt;LogRetentionDays&lt;/code&gt; que varia por ambiente: 7 dias em dev, 30 em staging, 90 em prod. Ou o que compliance exigir, desde que não seja infinito.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.4 Log level por ambiente
&lt;/h3&gt;

&lt;p&gt;DEBUG em produção é a forma mais eficiente de dobrar sua conta de logs. Controle via Mappings no SAM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Mappings&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;EnvConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;dev&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;LogLevel&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DEBUG&lt;/span&gt;
    &lt;span class="na"&gt;staging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;LogLevel&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;INFO&lt;/span&gt;
    &lt;span class="na"&gt;prod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;LogLevel&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;INFO&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4.0 Um debug real: dois bugs atrás de uma mensagem genérica
&lt;/h2&gt;

&lt;p&gt;Isso aconteceu comigo enquanto preparava o projeto de exemplo deste artigo. Deixo aqui porque é um caso melhor do que qualquer exemplo que eu inventaria.&lt;/p&gt;

&lt;p&gt;Depois do primeiro deploy bem-sucedido, testei o &lt;code&gt;POST /pedidos&lt;/code&gt; e recebi:&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="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Internal Erro. Try again."&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;Sem mais detalhes. E propositalmente sem mais detalhes o handler captura qualquer exceção genérica e devolve essa mensagem amigável de propósito, porque uma API não deveria vazar stack trace para quem está do outro lado. Isso é certo do ponto de vista de segurança. Também significa que, sem observabilidade decente no backend, você fica cego.&lt;/p&gt;

&lt;p&gt;Fui direto para o CloudWatch Logs Insights:&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="n"&gt;filter&lt;/span&gt; &lt;span class="k"&gt;level&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;"ERROR"&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ocorrencias&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;tipo_erro&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;operacao&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;sort&lt;/span&gt; &lt;span class="n"&gt;ocorrencias&lt;/span&gt; &lt;span class="k"&gt;desc&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E o log estruturado entregou na hora:&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;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ERROR"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"operacao"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"criar_pedido"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tipo_erro"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TypeError"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mensagem_erro"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Float types are not supported. Use Decimal types instead."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pedido_id"&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;O DynamoDB, via boto3, não aceita &lt;code&gt;float&lt;/code&gt; nativo do Python, só &lt;code&gt;Decimal&lt;/code&gt;. O handler estava salvando &lt;code&gt;valor&lt;/code&gt; (&lt;code&gt;179.80&lt;/code&gt;) direto como veio do JSON. A função &lt;code&gt;processar_pedido&lt;/code&gt; já fazia essa conversão certinho, só a &lt;code&gt;criar_pedido&lt;/code&gt; tinha escapado. Corrigi convertendo para &lt;code&gt;Decimal(str(valor))&lt;/code&gt; apenas no item que vai para o DynamoDB, mantendo &lt;code&gt;float&lt;/code&gt; no dict usado para log, métrica e resposta HTTP, não queria efeito colateral na serialização JSON por causa de um tipo que só o DynamoDB exige.&lt;/p&gt;

&lt;p&gt;Testei de novo. Erro genérico de novo. Voltei no Insights, mesma query, e apareceu um segundo erro, completamente diferente do primeiro:&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;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ERROR"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"operacao"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"criar_pedido"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tipo_erro"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ClientError"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mensagem_erro"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"User: .../pedidos-criar-dev is not authorized to perform: events:PutEvents on resource: arn:aws:events:us-east-1:...:event-bus/default"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pedido_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;Esse foi mais sutil. O código tinha &lt;code&gt;os.environ.get("EVENT_BUS_NAME", "default")&lt;/code&gt;, um fallback razoável à primeira vista. O problema é que a variável &lt;code&gt;EVENT_BUS_NAME&lt;/code&gt; nunca tinha sido definida no template, então o fallback sempre disparava, e o código tentava publicar no event bus &lt;em&gt;padrão&lt;/em&gt; da conta. A IAM policy, corretamente, só liberava &lt;code&gt;PutEvents&lt;/code&gt; no bus customizado que o projeto realmente usa. Fallback silencioso mascarando uma variável de ambiente esquecida o tipo de bug que passa no code review porque parece defensivo.&lt;/p&gt;

&lt;p&gt;O que faz esse caso valer a pena contar não é nenhum dos dois bugs isoladamente. É que sem &lt;code&gt;tipo_erro&lt;/code&gt; e &lt;code&gt;mensagem_erro&lt;/code&gt; como campos estruturados e pesquisáveis, eu estaria lendo texto corrido no console tentando adivinhar onde, entre duas invocações, a causa mudou completamente. Com log estruturado, foram duas queries idênticas, dois resultados diferentes, dois fixes de dez minutos cada.&lt;/p&gt;

&lt;p&gt;É fácil ler a seção anterior sobre Powertools Logger e achar que &lt;code&gt;pedido_id&lt;/code&gt; e &lt;code&gt;cold_start&lt;/code&gt; automáticos são só conveniência. Na prática é a diferença entre filtrar por campo e grep em prosa.&lt;/p&gt;




&lt;h2&gt;
  
  
  5.0 Métricas: use o que é de graça antes de criar o que custa
&lt;/h2&gt;

&lt;p&gt;Tem muita coisa útil antes de você precisar criar uma métrica customizada.&lt;/p&gt;

&lt;p&gt;As métricas padrão do Lambda que mais uso no dia a dia:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Errors&lt;/code&gt; e &lt;code&gt;ErrorRate&lt;/code&gt;: taxa de erro por função. Bom para alarme de SLA.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Duration&lt;/code&gt; com estatísticas p50, p90, p99: distribuição de latência. Nunca use só a média, ela esconde outliers. Uma função com p50 de 100ms e p99 de 8000ms está com problema que a média não mostra.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Throttles&lt;/code&gt;: qualquer valor acima de zero em produção merece atenção imediata.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ConcurrentExecutions&lt;/code&gt;: para detectar burst antes de virar throttle.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;IteratorAge&lt;/code&gt;: se você tem função consumindo Kinesis ou DynamoDB Streams, isso é o lag do consumer. Subindo significa que você não está acompanhando o volume.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Todas gratuitas. Se essas cobrem o que você precisa, pare aqui.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.1 Embedded Metric Format: métricas customizadas sem overhead de API
&lt;/h3&gt;

&lt;p&gt;Quando você precisa de métrica customizada, o caminho certo é EMF, não &lt;code&gt;put_metric_data&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;O &lt;code&gt;put_metric_data&lt;/code&gt; faz uma chamada de API para o CloudWatch a cada invocação. Além do custo das chamadas, é uma dependência extra que pode falhar e adiciona latência.&lt;/p&gt;

&lt;p&gt;O EMF funciona diferente: você emite a métrica dentro do log JSON em um formato especial. O CloudWatch processa o log e cria a métrica automaticamente. Sem chamada de API extra, sem latência adicional.&lt;/p&gt;

&lt;p&gt;Com o Powertools:&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;aws_lambda_powertools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Metrics&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;aws_lambda_powertools.metrics&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MetricUnit&lt;/span&gt;

&lt;span class="n"&gt;metrics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Metrics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;namespace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MeuApp/Pedidos&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pedidos&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@metrics.log_metrics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;capture_cold_start_metric&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Isso vira uma métrica CloudWatch via EMF — sem put_metric_data
&lt;/span&gt;    &lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_metric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PedidoProcessado&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;MetricUnit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_dimension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aprovado&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;capture_cold_start_metric=True&lt;/code&gt; emite uma métrica &lt;code&gt;ColdStart&lt;/code&gt; automaticamente. De graça, sem código extra.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.2 A armadilha das dimensões de alta cardinalidade
&lt;/h3&gt;

&lt;p&gt;Métricas customizadas com dimensão de alta cardinalidade são uma das formas mais rápidas de explodir o custo do CloudWatch.&lt;/p&gt;

&lt;p&gt;Exemplo: você tem 1000 clientes e quer ver quantos pedidos cada um fez.&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;# ERRADO — 1000 métricas × $0,30 = $300/mês
&lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_dimension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ClienteId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;cliente_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_metric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PedidoCliente&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;MetricUnit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para esse caso, use Logs Insights:&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="n"&gt;filter&lt;/span&gt; &lt;span class="n"&gt;mensagem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;"Pedido processado"&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;cliente_id&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;sort&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="k"&gt;desc&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A consulta custa por GB escaneado, mas você só roda quando precisa. Dimensões de métrica fazem sentido quando a cardinalidade é baixa: status, tipo, ambiente, região. Não por cliente, não por produto individual, não por request ID.&lt;/p&gt;




&lt;h2&gt;
  
  
  6.0 X-Ray: o que ele mostra que os logs não mostram
&lt;/h2&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl5c20xrtags6y0t647v6.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl5c20xrtags6y0t647v6.png" alt=" " width="799" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Os logs dizem que uma invocação levou 800ms. O X-Ray diz que desses 800ms, 12ms foram no seu código, 750ms foram esperando o DynamoDB responder, e os outros 38ms foram no cold start.&lt;/p&gt;

&lt;p&gt;Essa diferença muda completamente onde você vai olhar para resolver o problema.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.1 capture_method: subsegmentos sem boilerplate
&lt;/h3&gt;

&lt;p&gt;Com o Powertools Tracer, você adiciona subsegmentos com um decorator:&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;aws_lambda_powertools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Tracer&lt;/span&gt;

&lt;span class="n"&gt;tracer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Tracer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pedidos&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@tracer.capture_lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;capture_response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;pedido&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;_parse_body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;_salvar_pedido&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pedido&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;_publicar_evento&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pedido&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@tracer.capture_method&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_salvar_pedido&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pedido&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# O X-Ray vai mostrar exatamente quanto tempo este método levou
&lt;/span&gt;    &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Item&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;pedido&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@tracer.capture_method&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_publicar_evento&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pedido&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;eventbridge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put_events&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;capture_response=False&lt;/code&gt; evita que o response completo vá para o trace, o que pode incluir dados sensíveis e aumentar custo de armazenamento.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.2 put_annotation: filtrando traces que importam
&lt;/h3&gt;

&lt;p&gt;Anotações no X-Ray permitem filtrar traces por valor de negócio:&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="n"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put_annotation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status_pedido&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put_annotation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;valor_acima_limite&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;valor&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No console do X-Ray, você consegue filtrar &lt;code&gt;annotation.status_pedido = "rejeitado"&lt;/code&gt; e ver só os traces de pedidos rejeitados. Muito mais útil do que varrer logs para encontrar o mesmo padrão.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.3 Sampling rules: a configuração que mais gente ignora
&lt;/h3&gt;

&lt;p&gt;O padrão da AWS é 1 req/seg fixo mais 5% do restante. Para a maioria das funções está OK. O problema é quando você tem funções de alto volume.&lt;/p&gt;

&lt;p&gt;Configure sampling rules por função ou por padrão de ARN:&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;"RuleName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HighVolumeFunction"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Priority"&lt;/span&gt;&lt;span class="p"&gt;:&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="nl"&gt;"FixedRate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ReservoirSize"&lt;/span&gt;&lt;span class="p"&gt;:&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="nl"&gt;"ServiceName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pedidos-processar-*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ResourceARN"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;1% para função de alto volume que você já conhece bem é diferente de 5% para uma função nova que pode ter comportamento inesperado. Trate isso por função, não como configuração global.&lt;/p&gt;

&lt;p&gt;Tem casos onde faz sentido desligar o X-Ray completamente: função de health check, processamento homogêneo de fila onde todos os traces são idênticos, função de baixa complexidade sem downstream calls. Nesses casos, logs estruturados com &lt;code&gt;Duration&lt;/code&gt; no CloudWatch cobrem o que importa.&lt;/p&gt;




&lt;h2&gt;
  
  
  7.0 Alarmes: o que monitorar de verdade
&lt;/h2&gt;

&lt;p&gt;Um dashboard sem alarme é decoração. Você precisa ser avisado quando algo sai do normal, não descobrir na próxima vez que abrir o console.&lt;/p&gt;

&lt;p&gt;Três alarmes que eu considero mínimo obrigatório por função crítica:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Taxa de erro:&lt;/strong&gt; monitore &lt;code&gt;Errors&lt;/code&gt;, não &lt;code&gt;ErrorRate&lt;/code&gt;, porque &lt;code&gt;ErrorRate&lt;/code&gt; é percentual e pode mascarar volume real. Se você tem 0,1% de erro em 1 milhão de invocações, são 1000 erros. Coloque um alarme absoluto e um relativo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Latência p99:&lt;/strong&gt; não p50, não média. O p99 mostra o que seus usuários mais lentos estão experimentando. Um alarme em 80% do timeout configurado dá tempo de agir antes de começar a ter timeouts reais:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;CriarPedidoDurationAlarm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::CloudWatch::Alarm&lt;/span&gt;
  &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;MetricName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Duration&lt;/span&gt;
    &lt;span class="na"&gt;ExtendedStatistic&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;p99&lt;/span&gt;
    &lt;span class="na"&gt;Threshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;24000&lt;/span&gt;  &lt;span class="c1"&gt;# 80% de 30s = 24000ms&lt;/span&gt;
    &lt;span class="na"&gt;ComparisonOperator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GreaterThanThreshold&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Throttles:&lt;/strong&gt; qualquer valor acima de zero em produção. Throttle significa que você está descartando tráfego. É uma emergência, não uma métrica para acompanhar.&lt;/p&gt;




&lt;h2&gt;
  
  
  8.0 Dashboard: visão consolidada, não por função
&lt;/h2&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcbhdexlia2kcl80xqpys.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcbhdexlia2kcl80xqpys.png" alt=" " width="798" height="213"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Um dashboard por função não serve. Quando algo dá errado em produção, você quer ver o sistema inteiro, não abrir 12 dashboards em abas diferentes.&lt;/p&gt;

&lt;p&gt;Monte um dashboard por stack ou por domínio de negócio com:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Taxa de erro de todas as funções em um gráfico só&lt;/li&gt;
&lt;li&gt;Latência p99 de todas as funções&lt;/li&gt;
&lt;li&gt;Invocações totais por função&lt;/li&gt;
&lt;li&gt;Throttles&lt;/li&gt;
&lt;li&gt;Métricas de negócio via EMF (pedidos criados, processados, rejeitados)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;O template SAM do projeto de exemplo gera esse dashboard via CloudFormation. Nada para configurar no console, versionado junto com a infraestrutura.&lt;/p&gt;




&lt;h2&gt;
  
  
  9.0 Checklist antes de ir para produção
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ ] LogFormat: JSON em todas as funções (template Globals)
[ ] ApplicationLogLevel: INFO em prod (nunca DEBUG)
[ ] log_event=False em prod (controle por variável de ambiente)
[ ] RetentionInDays configurado em todos os log groups
[ ] Métricas de Duration usando p99, não média
[ ] Sampling rule do X-Ray revisada para funções de alto volume
[ ] capture_response=False no Tracer em prod
[ ] Alarme em Errors (absoluto)
[ ] Alarme em Duration p99 (&amp;gt; 80% do timeout)
[ ] Alarme em Throttles (qualquer valor &amp;gt; 0)
[ ] Dashboard consolidado por stack
[ ] Budget Alert no Cost Explorer separando CloudWatch dos outros serviços
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O último item parece paranoia até deixar de ser.&lt;/p&gt;




&lt;h2&gt;
  
  
  10.0 Projeto de exemplo
&lt;/h2&gt;

&lt;p&gt;O repositório que acompanha este artigo tem uma API de pedidos completa com quatro funções Lambda demonstrando cada conceito na prática:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;criar_pedido&lt;/code&gt;: POST /pedidos com Logger, Tracer e Metrics configurados&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;consultar_pedido&lt;/code&gt;: GET /pedidos/{id} com anotações X-Ray e métricas de hit/miss&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;processar_pedido&lt;/code&gt;: consumidor EventBridge com métricas de negócio por dimensão de status&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;decidir_pedido&lt;/code&gt;: PATCH /pedidos/{id}/aprovar e /rejeitar, aprovação e rejeição manual de pedidos que ficaram em &lt;code&gt;aguardando_aprovacao&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A quarta função existe porque a classificação automática por valor deixava um estado sem saída: pedidos na faixa intermediária ficavam parados em &lt;code&gt;aguardando_aprovacao&lt;/code&gt; sem nenhum jeito de sair dali. As duas rotas aprovar e rejeitar são atendidas pela mesma Lambda, com a ação derivada do path da requisição. Menos uma função duplicada, menos um cold start a mais para gerenciar.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Regra de negócio (classificação automática por valor):&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Valor do pedido&lt;/th&gt;
&lt;th&gt;Status resultante&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;≤ R$ 500,00&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;aprovado&lt;/code&gt; (automático)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R$ 500,01 – R$ 5.000,00&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;aguardando_aprovacao&lt;/code&gt; (precisa de &lt;code&gt;/aprovar&lt;/code&gt; ou &lt;code&gt;/rejeitar&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;gt; R$ 5.000,00&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;rejeitado&lt;/code&gt; (automático)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Endpoints:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Método&lt;/th&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Função&lt;/th&gt;
&lt;th&gt;Notas&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/pedidos&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;criar_pedido&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Publica &lt;code&gt;PedidoCriado&lt;/code&gt; no EventBridge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/pedidos/{id}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;consultar_pedido&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;404 se não existir&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PATCH&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/pedidos/{id}/aprovar&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;decidir_pedido&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;409 se o pedido não estiver em &lt;code&gt;aguardando_aprovacao&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PATCH&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/pedidos/{id}/rejeitar&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;decidir_pedido&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;409 se o pedido não estiver em &lt;code&gt;aguardando_aprovacao&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A transição de status usa &lt;code&gt;ConditionExpression&lt;/code&gt; no &lt;code&gt;update_item&lt;/code&gt; do DynamoDB o mesmo padrão de idempotência do &lt;code&gt;processar_pedido&lt;/code&gt; para garantir que a decisão só é aplicada uma vez, mesmo com requisições concorrentes. Quando a condição falha, uma leitura simples desambigua entre "pedido não existe" (404) e "pedido já está em outro status" (409, com o status atual na mensagem).&lt;/p&gt;

&lt;p&gt;A observabilidade segue o mesmo padrão das outras funções: métrica EMF &lt;code&gt;PedidoDecididoManualmente&lt;/code&gt; com dimensão &lt;code&gt;Decisao&lt;/code&gt; (aprovado/rejeitado), anotação X-Ray &lt;code&gt;decisao_manual&lt;/code&gt;, e logs estruturados com &lt;code&gt;pedido_id&lt;/code&gt;, &lt;code&gt;acao&lt;/code&gt; e &lt;code&gt;novo_status&lt;/code&gt;. O dashboard ganhou um widget novo para essas decisões, e o widget "Pedidos por Status" foi corrigido para incluir a série &lt;code&gt;aguardando_aprovacao&lt;/code&gt; faltava no dashboard original, o que por si só é um bom lembrete de revisar dashboard quando o domínio de negócio ganha um status novo.&lt;/p&gt;

&lt;p&gt;O template SAM inclui log groups com retenção, alarmes em p99 e throttles, dashboard CloudWatch e Mappings de configuração por ambiente.&lt;/p&gt;

&lt;p&gt;Para rodar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sam build &lt;span class="nt"&gt;--use-container&lt;/span&gt;
sam deploy &lt;span class="nt"&gt;--config-env&lt;/span&gt; dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As queries prontas para o Logs Insights estão em &lt;code&gt;monitoring/insights-queries.md&lt;/code&gt; no repositório.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;O que este exemplo ainda não cobre&lt;/strong&gt;, para ser honesto: os alarmes de erro e duração hoje existem só para &lt;code&gt;criar_pedido&lt;/code&gt;, &lt;code&gt;decidir_pedido&lt;/code&gt; e &lt;code&gt;consultar_pedido&lt;/code&gt; ainda não têm réplica. Também não há notificação (SNS ou e-mail) quando um pedido entra em &lt;code&gt;aguardando_aprovacao&lt;/code&gt; hoje isso só é visível consultando a API ou o dashboard. E não existem testes automatizados. Nenhum desses pontos invalida o que foi demonstrado sobre observabilidade, mas vale deixar claro que é um projeto de exemplo, não um sistema pronto para produção.&lt;/p&gt;

&lt;p&gt;🔗 Repositório com os exemplos: &lt;a href="https://github.com/diegobroetto/observability-demo" rel="noopener noreferrer"&gt;https://github.com/diegobroetto/observability-demo&lt;/a&gt;&lt;/p&gt;




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

&lt;p&gt;Observabilidade serverless cara geralmente é observabilidade descuidada. DEBUG em produção, retenção infinita, X-Ray sem sampling rule em função de alto volume, métrica customizada com dimensão de alta cardinalidade qualquer um desses sozinho não parece grande coisa. Junto viram uma fatura que você não esperava.&lt;/p&gt;

&lt;p&gt;O Powertools for Lambda resolve 80% do trabalho sem boilerplate. O resto é configuração intencional no template: retenção por ambiente, log level por ambiente, sampling por tipo de função.&lt;/p&gt;

&lt;p&gt;E configure os alarmes antes de precisar deles. O dashboard você abre quando quer. O alarme te acorda quando você precisa.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Diego Broetto</dc:creator>
      <pubDate>Thu, 11 Jun 2026 20:36:29 +0000</pubDate>
      <link>https://dev.to/broettod/-27hh</link>
      <guid>https://dev.to/broettod/-27hh</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/broettod/sam-cli-language-extensions-o-fim-do-copy-paste-em-templates-serverless-33n" class="crayons-story__hidden-navigation-link"&gt;SAM CLI + Language Extensions: o fim do copy-paste em templates serverless&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/broettod" class="crayons-avatar  crayons-avatar--l  "&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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2972664%2F3a9f248e-ec85-4254-b34c-7e3dbe50465e.jpg" alt="broettod profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/broettod" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Diego Broetto
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Diego Broetto
                
                
              
              &lt;div id="story-author-preview-content-3876245" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/broettod" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2972664%2F3a9f248e-ec85-4254-b34c-7e3dbe50465e.jpg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Diego Broetto&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/broettod/sam-cli-language-extensions-o-fim-do-copy-paste-em-templates-serverless-33n" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jun 11&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/broettod/sam-cli-language-extensions-o-fim-do-copy-paste-em-templates-serverless-33n" id="article-link-3876245"&gt;
          SAM CLI + Language Extensions: o fim do copy-paste em templates serverless
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/aws"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;aws&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/serverless"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;serverless&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/cloudformation"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;cloudformation&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/braziliandevs"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;braziliandevs&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/broettod/sam-cli-language-extensions-o-fim-do-copy-paste-em-templates-serverless-33n" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;8&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/broettod/sam-cli-language-extensions-o-fim-do-copy-paste-em-templates-serverless-33n#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              4&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            9 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>SAM CLI + Language Extensions: o fim do copy-paste em templates serverless</title>
      <dc:creator>Diego Broetto</dc:creator>
      <pubDate>Thu, 11 Jun 2026 20:04:33 +0000</pubDate>
      <link>https://dev.to/broettod/sam-cli-language-extensions-o-fim-do-copy-paste-em-templates-serverless-33n</link>
      <guid>https://dev.to/broettod/sam-cli-language-extensions-o-fim-do-copy-paste-em-templates-serverless-33n</guid>
      <description>&lt;h2&gt;
  
  
  1.0 Introdução
&lt;/h2&gt;

&lt;p&gt;Se você já trabalhou com AWS SAM em projetos com múltiplos ambientes, provavelmente já viveu essa cena: precisa adicionar uma nova função Lambda ao projeto, abre o &lt;code&gt;template.yaml&lt;/code&gt;, encontra o bloco da função existente, seleciona tudo, cola abaixo, começa a trocar os valores um por um. Nome da função, variável de ambiente, tag, CodeUri. Torcer pra não esquecer nada.&lt;/p&gt;

&lt;p&gt;Um tempo depois, alguém aumenta o timeout de uma das funções e esquece de replicar nas outras duas. É um caso bem comum e que já vi se repetir em vários clientes do meu dia a dia.&lt;/p&gt;

&lt;p&gt;Esse problema se chama &lt;strong&gt;copy-paste em IaC&lt;/strong&gt;. E no SAM, ele era difícil de resolver.&lt;/p&gt;

&lt;p&gt;Quem usa CloudFormation no dia a dia sabe que desde 2023 o &lt;code&gt;AWS::LanguageExtension&lt;/code&gt; com &lt;code&gt;Fn::ForEach&lt;/code&gt; permite gerar múltiplos recursos a partir de uma única definição. O problema é que o SAM CLI simplesmente não entendia esse bloco. Você tentava rodar um &lt;code&gt;sam local invoke&lt;/code&gt; com um template que usava &lt;code&gt;Fn::ForEach&lt;/code&gt; e levava um erro. O resultado era uma escolha ingrata: templates DRY ou desenvolvimento local com o SAM. Não dava pra ter os dois.&lt;/p&gt;

&lt;p&gt;Em maio de 2026, a AWS resolveu isso. O SAM CLI agora processa Language Extensions em memória durante as operações locais e o template original segue intacto para o deploy no CloudFormation.&lt;/p&gt;

&lt;p&gt;Nesse artigo vou mostrar como migrar esses templates repetitivos para uma estrutura gerada com &lt;code&gt;Fn::ForEach&lt;/code&gt;, com exemplos que você pode testar agora mesmo no seu ambiente.&lt;/p&gt;




&lt;h2&gt;
  
  
  2.0 Contexto
&lt;/h2&gt;

&lt;p&gt;Antes de entrar no código, vale entender por que esse problema durou tanto tempo.&lt;/p&gt;

&lt;p&gt;O &lt;code&gt;AWS::LanguageExtensions&lt;/code&gt; é um transform do CloudFormation lançado em 2023. Ele desbloqueia um conjunto de funções que não estão disponíveis por padrão nos templates, a principal delas sendo o &lt;code&gt;Fn::ForEach&lt;/code&gt;: você itera sobre uma lista e gera múltiplos recursos a partir de uma única definição. Parecido com um &lt;code&gt;for&lt;/code&gt; em qualquer linguagem de programação.&lt;/p&gt;

&lt;p&gt;O problema é que o SAM CLI e o CloudFormation são camadas diferentes. Quando você roda &lt;code&gt;sam local invoke&lt;/code&gt; ou &lt;code&gt;sam build&lt;/code&gt;, o SAM CLI processa o template localmente, sem passar pelo CloudFormation. E até maio de 2026, esse processamento local não sabia o que fazer com o bloco &lt;code&gt;Fn::ForEach&lt;/code&gt;.&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%2Ftl5cytqkciprzqekr8ab.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%2Ftl5cytqkciprzqekr8ab.png" alt="Erro do SAM CLI ao tentar processar um template com Fn::ForEach, exibindo traceback do Python e a mensagem enganosa de validação" width="800" height="67"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;O erro é totalmente enganoso, parece problema de indentação no YAML, mas na verdade é o SAM CLI não reconhecendo a sintaxe do Language Extensions. Quem já bateu nesse erro e ficou olhando pro template por meia hora sem achar nada errado sabe o quanto isso frustra.&lt;/p&gt;

&lt;p&gt;Isso criava um dilema real no dia a dia. Você tinha três caminhos, e nenhum era bom:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Usar Language Extensions e abrir mão do SAM CLI local: deploy direto no CloudFormation para testar. Ciclo lento, custo de iteração alto, debug difícil.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Usar SAM CLI local e abrir mão do DRY: manter os blocos repetidos no template, aceitar o risco de inconsistência entre ambientes e torcer pra ninguém esquecer de replicar uma mudança.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Manter dois templates: um enxuto com &lt;code&gt;Fn::ForEach&lt;/code&gt; para o deploy, outro expandido manualmente para uso local. O pior dos mundos, duplicação do template inteiro, sincronização manual entre os dois, confusão garantida na revisão de código.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Eu ficava sempre na opção 2. O template ficava feio, mas pelo menos o &lt;code&gt;sam local invoke&lt;/code&gt; funcionava.&lt;/p&gt;

&lt;p&gt;O que a AWS fez em maio de 2026 foi processar as Language Extensions em memória, expandindo os loops antes de qualquer operação local. O template original no disco nunca é tocado, o CloudFormation continua recebendo o template com &lt;code&gt;Fn::ForEach&lt;/code&gt; intacto e faz a própria expansão no deploy.&lt;/p&gt;

&lt;p&gt;O SAM CLI cuida do desenvolvimento local, o CloudFormation cuida do deploy. Sem interferência entre os dois.&lt;/p&gt;




&lt;h2&gt;
  
  
  3.0 Testando na prática: antes e depois
&lt;/h2&gt;

&lt;p&gt;Vamos usar um cenário com três funções Lambda, uma por ambiente (DEV, HML e PROD). Cada função vai ter o mesmo código base, mas com variáveis de ambiente e configurações diferentes.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.1 O modo antigo: copy-paste inevitável
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;AWSTemplateFormatVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2010-09-09"&lt;/span&gt;
&lt;span class="na"&gt;Transform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless-2016-10-31&lt;/span&gt;

&lt;span class="na"&gt;Globals&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Function&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Runtime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python3.13&lt;/span&gt;
    &lt;span class="na"&gt;MemorySize&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;256&lt;/span&gt;
    &lt;span class="na"&gt;Timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;

&lt;span class="na"&gt;Resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

  &lt;span class="na"&gt;DevFunction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless::Function&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;FunctionName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hello-world-dev&lt;/span&gt;
      &lt;span class="na"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./src/dev/&lt;/span&gt;
      &lt;span class="na"&gt;Handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;handler.lambda_handler&lt;/span&gt;
      &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ambiente&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;DEV"&lt;/span&gt;
      &lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;ENV&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dev&lt;/span&gt;
          &lt;span class="na"&gt;TIER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;free&lt;/span&gt;
      &lt;span class="na"&gt;Tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dev&lt;/span&gt;
        &lt;span class="na"&gt;Project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hello-word-old&lt;/span&gt;

  &lt;span class="na"&gt;HmlFunction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless::Function&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;FunctionName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;demo-old-hml&lt;/span&gt;
      &lt;span class="na"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./src/hml/&lt;/span&gt;
      &lt;span class="na"&gt;Handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;handler.lambda_handler&lt;/span&gt;
      &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ambiente&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;hml"&lt;/span&gt;
      &lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;ENV&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hml&lt;/span&gt;
          &lt;span class="na"&gt;TIER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;standard&lt;/span&gt;
      &lt;span class="na"&gt;Tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hml&lt;/span&gt;
        &lt;span class="na"&gt;Project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hello-word-old&lt;/span&gt;

  &lt;span class="na"&gt;ProdFunction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless::Function&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;FunctionName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;demo-old-prod&lt;/span&gt;
      &lt;span class="na"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./src/prod/&lt;/span&gt;
      &lt;span class="na"&gt;Handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;handler.lambda_handler&lt;/span&gt;
      &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ambiente&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Prod"&lt;/span&gt;
      &lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;ENV&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prod&lt;/span&gt;
          &lt;span class="na"&gt;TIER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;premium&lt;/span&gt;
      &lt;span class="na"&gt;Tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prod&lt;/span&gt;
        &lt;span class="na"&gt;Project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hello-word-old&lt;/span&gt;

&lt;span class="na"&gt;Outputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;AlphaFunctionArn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;DevFunction.Arn&lt;/span&gt;
  &lt;span class="na"&gt;hmlFunctionArn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;HmlFunction.Arn&lt;/span&gt;
  &lt;span class="na"&gt;ProdFunctionArn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;ProdFunction.Arn&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;São três blocos quase idênticos. Se você precisar mudar o runtime de &lt;code&gt;python3.13&lt;/code&gt; para &lt;code&gt;python3.14&lt;/code&gt;, precisa alterar em três lugares, ou no &lt;code&gt;Globals&lt;/code&gt;, mas só funciona para propriedades suportadas pelo bloco global. Qualquer propriedade que não vai para &lt;code&gt;Globals&lt;/code&gt; vira copy-paste obrigatório.&lt;/p&gt;

&lt;p&gt;E se precisar adicionar um quarto ambiente (&lt;code&gt;Staging&lt;/code&gt;)? Copiar um dos blocos, renomear, ajustar variáveis, criar o &lt;code&gt;src/stg/&lt;/code&gt;, adicionar o Output manualmente. São no mínimo seis pontos de toque para uma mudança que deveria ser trivial.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.2 O modo novo: uma definição, N funções
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;AWSTemplateFormatVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2010-09-09"&lt;/span&gt;

&lt;span class="na"&gt;Transform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;AWS::LanguageExtensions&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless-2016-10-31&lt;/span&gt;

&lt;span class="na"&gt;Parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Environments&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CommaDelimitedList&lt;/span&gt;
    &lt;span class="na"&gt;Default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Dev,Hml,Prod"&lt;/span&gt;

&lt;span class="na"&gt;Globals&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Function&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Runtime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python3.13&lt;/span&gt;
    &lt;span class="na"&gt;MemorySize&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;256&lt;/span&gt;
    &lt;span class="na"&gt;Timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;

&lt;span class="na"&gt;Mappings&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;EnvConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;dev&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Tier&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;free&lt;/span&gt;
      &lt;span class="na"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./src/dev/&lt;/span&gt;
    &lt;span class="na"&gt;hml&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Tier&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;standard&lt;/span&gt;
      &lt;span class="na"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./src/hml/&lt;/span&gt;
    &lt;span class="na"&gt;prod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Tier&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;premium&lt;/span&gt;
      &lt;span class="na"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./src/prod/&lt;/span&gt;

&lt;span class="na"&gt;Resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Fn::ForEach::CreateFunctions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;EnvName&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;Environments&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;${EnvName}Function&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless::Function&lt;/span&gt;
        &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;FunctionName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;Fn::Sub&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hello-world-${EnvName}"&lt;/span&gt;
          &lt;span class="na"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;Fn::FindInMap&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;EnvConfig&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;EnvName&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CodeUri&lt;/span&gt;
          &lt;span class="na"&gt;Handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;handler.lambda_handler&lt;/span&gt;
          &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;Fn::Sub&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Funcao&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;do&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ambiente&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;${EnvName}"&lt;/span&gt;
          &lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;Variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;ENV&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;Ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;EnvName&lt;/span&gt;
              &lt;span class="na"&gt;TIER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;Fn::FindInMap&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;EnvConfig&lt;/span&gt;
                  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;EnvName&lt;/span&gt;
                  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Tier&lt;/span&gt;
          &lt;span class="na"&gt;Tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;Ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;EnvName&lt;/span&gt;
            &lt;span class="na"&gt;Project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hello-word&lt;/span&gt;

&lt;span class="na"&gt;Outputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Fn::ForEach::FunctionOutputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;EnvName&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;Environments&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;${EnvName}FunctionArn&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;Fn::Sub&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ARN&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;da&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;funcao&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;${EnvName}"&lt;/span&gt;
        &lt;span class="na"&gt;Value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;Fn::GetAtt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;${EnvName}Function&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Arn&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O &lt;code&gt;Fn::ForEach::CreateFunctions&lt;/code&gt; itera sobre &lt;code&gt;[hml, dev, prod]&lt;/code&gt; e gera automaticamente &lt;code&gt;DevFunction&lt;/code&gt;, &lt;code&gt;HmlFunction&lt;/code&gt; e &lt;code&gt;ProdFunction&lt;/code&gt;, cada uma com as configurações corretas vindas do &lt;code&gt;Mappings&lt;/code&gt;. Os Outputs também são gerados pelo segundo &lt;code&gt;ForEach&lt;/code&gt;, sem nenhum bloco manual.&lt;/p&gt;

&lt;p&gt;O handler Python é o mesmo para todos os ambientes, todas as diferenças chegam via variáveis de ambiente:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;env&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ENV&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unknown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;tier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TIER&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;standard&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Oi, eu sou o ambiente de &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tier&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;env&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&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;
  
  
  4.0 Testando Localmente
&lt;/h2&gt;

&lt;h3&gt;
  
  
  4.1 Pré-requisitos
&lt;/h3&gt;

&lt;p&gt;Faça a atualização do seu AWS SAM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;aws-sam-cli &lt;span class="nt"&gt;--upgrade&lt;/span&gt;
sam &lt;span class="nt"&gt;--version&lt;/span&gt;  &lt;span class="c"&gt;# 1.161.1 ou superior&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fh090zre9lo3cn08bdytv.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%2Fh090zre9lo3cn08bdytv.png" alt="Terminal exibindo a saída do comando sam --version com a versão atualizada do SAM CLI" width="450" height="84"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4.2 Estrutura de pastas
&lt;/h3&gt;

&lt;p&gt;Vou disponibilizar no final do artigo um link para o repositório do github onde você pode fazer um clone e realizar os testes.&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%2Fanklx9yu9251jgi2904b.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%2Fanklx9yu9251jgi2904b.png" alt="Estrutura de pastas do projeto com os diretórios modo-antigo e modo-novo, contendo templates e código fonte por ambiente" width="423" height="373"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4.3 Testando o modo antigo
&lt;/h3&gt;

&lt;p&gt;Faça a validação do template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;modo-antigo/

sam validate &lt;span class="nt"&gt;--template&lt;/span&gt; template-old.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fwwhgxlald379q1h32eq5.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%2Fwwhgxlald379q1h32eq5.png" alt="Terminal exibindo a saída do sam validate confirmando que o template-old.yaml é um template SAM válido" width="800" height="82"&gt;&lt;/a&gt;&lt;br&gt;
Após terminar, execute o build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sam build &lt;span class="nt"&gt;--template&lt;/span&gt; template-old.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fs7obi5sdv5zylx9r8aqz.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%2Fs7obi5sdv5zylx9r8aqz.png" alt="Terminal exibindo a saída do sam build com o build das três funções concluído com sucesso" width="800" height="330"&gt;&lt;/a&gt;&lt;br&gt;
Agora vamos testar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sam &lt;span class="nb"&gt;local &lt;/span&gt;invoke DevFunction &lt;span class="nt"&gt;--no-event&lt;/span&gt;
sam &lt;span class="nb"&gt;local &lt;/span&gt;invoke HmlFunction &lt;span class="nt"&gt;--no-event&lt;/span&gt;
sam &lt;span class="nb"&gt;local &lt;/span&gt;invoke ProdFunction &lt;span class="nt"&gt;--no-event&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2F9c4kydaa6ed5k8f90fwv.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%2F9c4kydaa6ed5k8f90fwv.png" alt="Terminal exibindo a saída do sam local invoke da DevFunction com a resposta JSON retornando ambiente dev e tier free" width="800" height="206"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4.4 Testando o modo novo
&lt;/h3&gt;

&lt;p&gt;Acesse o repositório e valide o template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;modo-novo/
&lt;span class="c"&gt;# flag --language-extensions obrigatoria em todos os comandos locais&lt;/span&gt;
sam validate &lt;span class="nt"&gt;--template&lt;/span&gt; template.yaml &lt;span class="nt"&gt;--language-extensions&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fs8z14myf465jhojdpnpg.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%2Fs8z14myf465jhojdpnpg.png" alt="Terminal exibindo a saída do sam validate confirmando que o template.yaml é um template SAM válido" width="800" height="82"&gt;&lt;/a&gt;&lt;br&gt;
Novamente, vamos fazer o build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sam build &lt;span class="nt"&gt;--template&lt;/span&gt; template.yaml &lt;span class="nt"&gt;--language-extensions&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fh6mn7w01ynqxs0t7gg15.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%2Fh6mn7w01ynqxs0t7gg15.png" alt="Terminal exibindo a saída do sam build com o build das três funções concluído com sucesso" width="800" height="354"&gt;&lt;/a&gt;&lt;br&gt;
E por fim, testar novamente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# invocar pelo nome expandido pelo ForEach&lt;/span&gt;
sam &lt;span class="nb"&gt;local &lt;/span&gt;invoke DevFunction &lt;span class="nt"&gt;--language-extensions&lt;/span&gt; &lt;span class="nt"&gt;--no-event&lt;/span&gt;
sam &lt;span class="nb"&gt;local &lt;/span&gt;invoke HmlFunction &lt;span class="nt"&gt;--language-extensions&lt;/span&gt; &lt;span class="nt"&gt;--no-event&lt;/span&gt;
sam &lt;span class="nb"&gt;local &lt;/span&gt;invoke ProdFunction &lt;span class="nt"&gt;--language-extensions&lt;/span&gt; &lt;span class="nt"&gt;--no-event&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fplhpc8xxpqhkoc3lvdsl.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%2Fplhpc8xxpqhkoc3lvdsl.png" alt="Terminal exibindo a saída do sam local invoke da ProdFunction com a resposta JSON retornando ambiente" width="799" height="248"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4.5 E se eu precisar adicionar um novo ambiente?
&lt;/h3&gt;

&lt;p&gt;Esse é o momento que deixa o benefício mais concreto. Para adicionar um ambiente &lt;code&gt;Staging&lt;/code&gt; no modo novo, são apenas três mudanças:&lt;/p&gt;

&lt;h4&gt;
  
  
  4.5.1 Atualizar o parâmetro
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Environments&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CommaDelimitedList&lt;/span&gt;
    &lt;span class="na"&gt;Default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hml,Dev,Prod,Stg"&lt;/span&gt;  &lt;span class="c1"&gt;# adicionar Staging aqui&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  4.5.2 Adicionar entrada no Mappings
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Mappings&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;EnvConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# ... entradas existentes ...&lt;/span&gt;
    &lt;span class="na"&gt;Staging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Tier&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;standard&lt;/span&gt;
      &lt;span class="na"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./src/stg/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  4.5.3 Criar nova pasta e mover código
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;src/stg

&lt;span class="nb"&gt;cp &lt;/span&gt;src/hml/handler.py src/stg/handler.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  4.5.4 Conclusão
&lt;/h4&gt;

&lt;p&gt;O Output &lt;code&gt;StgFunctionArn&lt;/code&gt; é gerado automaticamente pelo segundo &lt;code&gt;ForEach&lt;/code&gt;. Nenhuma outra alteração necessária.&lt;/p&gt;

&lt;p&gt;No modo antigo, a mesma operação exigiria: copiar um bloco inteiro de ~15 linhas, renomear em todos os lugares, ajustar as variáveis, criar o diretório e o handler, e adicionar o Output manualmente. São pelo menos seis pontos de toque, cada um com risco de erro.&lt;/p&gt;




&lt;h2&gt;
  
  
  5.0 Outras funções que vieram junto
&lt;/h2&gt;

&lt;p&gt;Além do &lt;code&gt;Fn::ForEach&lt;/code&gt;, o SAM CLI passou a suportar mais funções do Language Extensions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Fn::Length&lt;/code&gt; retorna o número de itens de uma lista. Útil para definir &lt;code&gt;ReservedConcurrentExecutions&lt;/code&gt; ou &lt;code&gt;ProvisionedConcurrencyConfig&lt;/code&gt; baseado no tamanho dinâmico de uma lista de parâmetros.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Fn::ToJsonString&lt;/code&gt; serializa qualquer valor para string JSON. Prático para passar configurações complexas como variável de ambiente em formato JSON sem ter que escrever o JSON manualmente no template.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Fn::FindInMap&lt;/code&gt; com &lt;code&gt;DefaultValue&lt;/code&gt; permite definir um valor de fallback quando a chave não existe no mapeamento, sem precisar de um bloco &lt;code&gt;Conditions&lt;/code&gt; inteiro para cobrir o caso base. Muito útil para templates multi-região onde algumas regiões têm configurações específicas e as demais usam o padrão.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;DeletionPolicy&lt;/code&gt; e &lt;code&gt;UpdateReplacePolicy&lt;/code&gt; condicionais permitem usar &lt;code&gt;!If&lt;/code&gt; nessas políticas, algo que antes não era suportado. Por exemplo, deletar automaticamente em ambientes de dev e reter em produção, tudo no mesmo template.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  6.0 Pontos de atenção
&lt;/h2&gt;

&lt;p&gt;Algumas coisas que aprendi testando na prática e que o anúncio oficial não deixa claro:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1.&lt;/strong&gt; A flag &lt;code&gt;--language-extensions&lt;/code&gt; é obrigatória em todos os comandos locais. O suporte é opt-in: sem a flag, o SAM CLI ignora o Language Extensions e retorna aquele erro confuso de "All Resources must be Objects", que parece indentação mas não é. Todos os comandos precisam da flag: &lt;code&gt;sam validate&lt;/code&gt;, &lt;code&gt;sam build&lt;/code&gt;, &lt;code&gt;sam local invoke&lt;/code&gt;, &lt;code&gt;sam local start-api&lt;/code&gt; e &lt;code&gt;sam sync&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.&lt;/strong&gt; A ordem dos transforms importa. &lt;code&gt;AWS::LanguageExtensions&lt;/code&gt; deve vir obrigatoriamente antes de &lt;code&gt;AWS::Serverless-2016-10-31&lt;/code&gt; na lista. Invertido não funciona.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Correto&lt;/span&gt;
&lt;span class="na"&gt;Transform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;AWS::LanguageExtensions&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless-2016-10-31&lt;/span&gt;

&lt;span class="c1"&gt;# Errado&lt;/span&gt;
&lt;span class="na"&gt;Transform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless-2016-10-31&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;AWS::LanguageExtensions&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3.&lt;/strong&gt; Dentro do &lt;code&gt;Fn::ForEach&lt;/code&gt;, use sempre a forma longa das funções. A short-form YAML (&lt;code&gt;!Sub&lt;/code&gt;, &lt;code&gt;!FindInMap&lt;/code&gt;, &lt;code&gt;!Ref&lt;/code&gt;, &lt;code&gt;!GetAtt&lt;/code&gt;) não funciona dentro dos blocos do loop. Use a forma explícita: &lt;code&gt;Fn::Sub&lt;/code&gt;, &lt;code&gt;Fn::FindInMap&lt;/code&gt;, &lt;code&gt;Ref&lt;/code&gt;, &lt;code&gt;Fn::GetAtt&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Errado: short-form dentro do ForEach&lt;/span&gt;
&lt;span class="na"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!FindInMap&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;EnvConfig&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="nv"&gt;EnvName&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Correto: forma longa&lt;/span&gt;
&lt;span class="na"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Fn::FindInMap&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;EnvConfig&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;EnvName&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CodeUri&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4.&lt;/strong&gt; Sem comentários inline dentro do bloco &lt;code&gt;Fn::ForEach&lt;/code&gt;. O parser YAML perde o fio do template com comentários na mesma linha dos valores do loop. Mantenha os comentários fora do bloco ou remova-os.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5.&lt;/strong&gt; Não compatível com Lambda Managed Instances. Funções geradas pelo &lt;code&gt;Fn::ForEach&lt;/code&gt; não podem usar capacity provider. Se o seu projeto usa Lambda Managed Instances, esse recurso ainda não se aplica.&lt;/p&gt;




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

&lt;p&gt;Esse lançamento fecha um gap que existia há quase três anos: o CloudFormation suportava &lt;code&gt;Fn::ForEach&lt;/code&gt; desde julho de 2023, mas o SAM CLI ficou para trás, e as equipes tinham que escolher entre templates DRY e desenvolvimento local rápido.&lt;/p&gt;

&lt;p&gt;A solução foi processar o Language Extensions em memória durante operações locais sem tocar no template original. O CloudFormation faz sua própria expansão no deploy. Cada camada faz o seu trabalho.&lt;/p&gt;

&lt;p&gt;O único detalhe que o anúncio oficial não deixa explícito é que o suporte é opt-in via flag &lt;code&gt;--language-extensions&lt;/code&gt;. Sem ela, você vai continuar levando aquele erro de indentação que não tem nada a ver com indentação. Agora você sabe.&lt;/p&gt;

&lt;p&gt;Se quiser testar, os templates de exemplo desse artigo estão disponíveis no repositório linkado abaixo. Qualquer dúvida ou feedback, comenta aqui.&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;Repositório com os exemplos:&lt;/strong&gt; &lt;a href="https://github.com/diegobroetto/sam-language-extensions-demo" rel="noopener noreferrer"&gt;github.com/diegobroetto/sam-language-extensions-demo&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>cloudformation</category>
      <category>braziliandevs</category>
    </item>
  </channel>
</rss>
