<?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: He4rt Developers</title>
    <description>The latest articles on DEV Community by He4rt Developers (@he4rt).</description>
    <link>https://dev.to/he4rt</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F5490%2Fef471ad5-dbd9-40be-9951-743a6026d59c.png</url>
      <title>DEV Community: He4rt Developers</title>
      <link>https://dev.to/he4rt</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/he4rt"/>
    <language>en</language>
    <item>
      <title>O que uma usina nuclear tem a ver com o seu processo de QA?</title>
      <dc:creator>Alicia Marianne 🇧🇷 </dc:creator>
      <pubDate>Sun, 05 Apr 2026 12:02:52 +0000</pubDate>
      <link>https://dev.to/he4rt/o-que-uma-usina-nuclear-tem-a-ver-com-o-seu-processo-de-qa-103j</link>
      <guid>https://dev.to/he4rt/o-que-uma-usina-nuclear-tem-a-ver-com-o-seu-processo-de-qa-103j</guid>
      <description>&lt;p&gt;A gente sabe que testar e validar um software antes de ir para produção é importante. Mas você já parou para pensar no peso real que isso carrega?&lt;/p&gt;

&lt;p&gt;Recentemente, estava revendo a série &lt;em&gt;Chernobyl&lt;/em&gt;, e ela me fez refletir sobre muita coisa — especialmente sobre a forma como encaro minha área, sendo QA, e sobre a responsabilidade que ela traz. Resolvi compartilhar isso com vocês.&lt;/p&gt;

&lt;p&gt;Para quem não conhece, &lt;em&gt;Chernobyl&lt;/em&gt; é uma minissérie dramática lançada em 2019 que retrata o desastre nuclear ocorrido na usina de mesmo nome, na então União Soviética, em 26 de abril de 1986. A história acompanha os eventos logo após a explosão do reator número 4 — o caos, as tentativas do governo soviético de esconder a gravidade do acidente e o enorme esforço de cientistas, bombeiros, militares e trabalhadores que arriscaram, e muitas vezes perderam, suas vidas para evitar uma catástrofe ainda maior. A série também segue o cientista Valery Legasov, que tenta descobrir a verdadeira causa do acidente e expor a verdade por trás da tragédia.&lt;/p&gt;

&lt;p&gt;Mas o ponto aqui vai além da série.&lt;/p&gt;

&lt;p&gt;O que mais me chamou atenção foi o quanto aquela tragédia conversa com algo que vivemos diariamente no desenvolvimento de software: &lt;strong&gt;a responsabilidade nas decisões&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  O que aconteceu em Chernobyl?
&lt;/h2&gt;

&lt;p&gt;Mesmo sabendo que existem elementos dramatizados, dá para aprender muita coisa com esse desastre. Não sou física nuclear, mas vou tentar resumir o que aconteceu — porque foi justamente essa parte que mais me fez refletir sobre responsabilidade e tomada de decisão.&lt;/p&gt;

&lt;p&gt;Na madrugada do dia &lt;strong&gt;26 de abril de 1986&lt;/strong&gt;, os operadores da usina realizavam um &lt;strong&gt;teste de segurança no reator 4&lt;/strong&gt;. O objetivo era validar se, em caso de queda de energia, as turbinas ainda conseguiriam gerar eletricidade por alguns segundos — tempo suficiente até que os geradores de emergência fossem acionados. No papel, o teste parecia simples.&lt;/p&gt;

&lt;p&gt;O problema é que, para executá-lo, diversos sistemas de segurança foram desativados e a potência do reator foi reduzida para um nível muito abaixo do ideal. Foi aí que tudo começou a sair do controle.&lt;/p&gt;

&lt;p&gt;O reator utilizado era do tipo &lt;strong&gt;RBMK&lt;/strong&gt;, um modelo com uma falha crítica de projeto: em determinadas condições, quanto mais vapor era gerado dentro do sistema, maior ficava a potência do reator. Em vez de estabilizar, ele se tornava cada vez mais instável. Para piorar, o teste foi conduzido sob forte pressão da liderança, mesmo diante de sinais claros de que não era seguro continuar.&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%2Fk1ar1wiltc1iy8426lz9.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%2Fk1ar1wiltc1iy8426lz9.png" alt="Chernobyl" width="800" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ao longo da série, vemos os próprios operadores levantando preocupações — que foram ignoradas. Quando perceberam que a situação era crítica, acionaram o botão de desligamento de emergência. Em teoria, esse comando deveria encerrar a reação. Mas, por uma falha no design das barras de controle, o efeito inicial foi o oposto: a potência disparou. Em poucos segundos, temperatura e pressão subiram de forma descontrolada.&lt;/p&gt;

&lt;p&gt;O resultado foram duas explosões que destruíram o topo do reator, expuseram o núcleo à atmosfera e liberaram uma enorme quantidade de material radioativo. O incêndio que se seguiu espalhou radiação por boa parte da Europa, transformando Chernobyl no maior desastre nuclear da história.&lt;/p&gt;

&lt;p&gt;O que mais me impactou foi perceber que a tragédia não aconteceu por um único erro. Ela foi consequência de uma &lt;strong&gt;cadeia de decisões ruins&lt;/strong&gt;: falhas técnicas ignoradas, riscos mal avaliados e pessoas que não foram ouvidas.&lt;/p&gt;




&lt;h2&gt;
  
  
  E o que isso tem a ver com software?
&lt;/h2&gt;

&lt;p&gt;Depois de assistir à série, comecei a fazer um paralelo com a nossa área. Porque, no fim, quantas vezes um incidente em produção também não nasce da mesma forma?&lt;/p&gt;

&lt;p&gt;Nem sempre o problema vem de um único bug. Muitas vezes, ele é o resultado de uma sequência de decisões tomadas sem o devido cuidado: um requisito mal definido, um risco não mapeado, uma validação superficial, uma entrega apressada — ou um alerta levantado pelo time que acabou sendo ignorado.&lt;/p&gt;

&lt;p&gt;Foi aí que a série me fez enxergar algo que vai além do contexto dela: &lt;strong&gt;quando a pressa fala mais alto do que a análise, o custo quase sempre aparece depois&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  O maior problema: risco tratado de forma rasa
&lt;/h2&gt;

&lt;p&gt;Um dos maiores desafios que vejo hoje no desenvolvimento de software é justamente o planejamento e o levantamento de riscos feitos de maneira superficial.&lt;/p&gt;

&lt;p&gt;Tenho certeza de que você vai concordar: não tem coisa pior do que refazer algo ou ficar apagando incêndios que poderiam ter sido discutidos antes.&lt;/p&gt;

&lt;p&gt;Vivemos num mundo onde tempo é dinheiro. E é exatamente por isso que qualidade precisa estar presente desde o início — não como uma etapa final, mas como parte do processo.&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%2Fomhezjqhepvjcvwbmn1d.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%2Fomhezjqhepvjcvwbmn1d.png" alt="Cadeia de erros" width="800" height="729"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Como QA, vou compartilhar duas coisas que considero essenciais.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ouça sua equipe
&lt;/h3&gt;

&lt;p&gt;Independentemente da sua posição no time, ouça as pessoas ao seu redor.&lt;/p&gt;

&lt;p&gt;Ninguém conhece melhor o produto do que quem o construiu, testou e convive com ele diariamente. Antes de uma nova funcionalidade entrar ou de um teste ser executado, converse com o time.&lt;/p&gt;

&lt;p&gt;Escute quem desenvolveu. Escute o produto. Escute suporte. Escute quem está mais próximo do usuário.&lt;/p&gt;

&lt;p&gt;Muitas vezes, o risco já foi identificado por alguém. Ele só não foi ouvido.&lt;/p&gt;

&lt;h3&gt;
  
  
  Analise antes de agir
&lt;/h3&gt;

&lt;p&gt;Entender o impacto de uma mudança não é só uma questão técnica — é também uma questão de negócio.&lt;/p&gt;

&lt;p&gt;Por exemplo: você vai melhorar a query de uma API. Em teoria, isso pode não alterar nenhuma regra de negócio. Mas como isso afeta o usuário final? A performance realmente melhorou? Existe algum impacto colateral? Como vamos medir se essa mudança foi positiva ou negativa?&lt;/p&gt;

&lt;p&gt;Nem toda melhoria técnica gera melhoria de produto. E esse olhar crítico é parte fundamental do papel de QA.&lt;/p&gt;




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

&lt;p&gt;No fim, &lt;em&gt;Chernobyl&lt;/em&gt; me fez refletir sobre algo que vai muito além de uma série: &lt;strong&gt;a responsabilidade por trás de cada decisão que tomamos no dia a dia&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A gente não está lidando com um reator nuclear. Mas ainda assim lida com impacto real — no usuário, no negócio e no próprio time. Um risco ignorado, um teste mal planejado ou uma decisão tomada sem ouvir a equipe podem não gerar uma catástrofe, mas certamente geram problemas que poderiam ter sido evitados com mais atenção, diálogo e análise.&lt;/p&gt;

&lt;p&gt;Para mim, ser QA vai muito além de encontrar bugs.&lt;/p&gt;

&lt;p&gt;É questionar antes que o problema aconteça. É analisar cenários com olhar crítico. É antecipar riscos. É provocar conversas importantes dentro do time. É ajudar a construir decisões mais seguras e conscientes.&lt;/p&gt;

&lt;p&gt;No final, qualidade não é apenas sobre software funcionando.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;É sobre responsabilidade, colaboração e cuidado com tudo aquilo que criamos.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>braziliandevs</category>
      <category>testing</category>
      <category>qa</category>
      <category>software</category>
    </item>
    <item>
      <title>De front-end para UX, e de volta ao código: o que significa ser Design Engineer em 2026</title>
      <dc:creator>vitoriazzp</dc:creator>
      <pubDate>Fri, 03 Apr 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/he4rt/de-front-end-para-ux-e-de-volta-ao-codigo-o-que-significa-ser-design-engineer-em-2026-3j74</link>
      <guid>https://dev.to/he4rt/de-front-end-para-ux-e-de-volta-ao-codigo-o-que-significa-ser-design-engineer-em-2026-3j74</guid>
      <description>&lt;p&gt;&lt;strong&gt;Sou UX/UI designer, mas antes disso fui front-end.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Atuei cerca de 5 anos trabalhando com HTML e CSS, transformando layouts em páginas, entendendo hierarquia de informação e estrutura de interface. Depois, tomei o caminho oposto: migrei para UX/UI e agora completo 5 anos atuando em produtos, passando por fintech, utilities e atuando como Product Designer.&lt;/p&gt;

&lt;p&gt;Essa trajetória, de front para UX e agora voltando a se aproximar do código, é justamente o que me levou a me reconhecer em um termo que gosto bastante: &lt;strong&gt;Design Engineer&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Minha trajetória: 5 anos de front-end, 5 anos de UX
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Quando era front-end&lt;/strong&gt;, eu via a tela como um resultado de código: HTML estruturando a informação, CSS dando forma e layout, um pouco de JavaScript dando comportamento.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quando migrei para UX&lt;/strong&gt;, passei a olhar mais para o todo do produto: pesquisa, fluxos, contexto do usuário, design systems, governança, revisão de interfaces, conversa com times de produto e de negócios.&lt;br&gt;
Hoje, percebo que essas duas visões não são opostas. &lt;em&gt;Elas se completam.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  O que é design engineer em 2026?
&lt;/h2&gt;

&lt;p&gt;Se você pesquisar sobre "Design Engineer", vai encontrar muitas definições técnicas, mas na prática o que mais faz sentido para mim é:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A pessoa que entende UX, código e um pouco de backend ao mesmo tempo, e usa isso para desenhar interfaces que são pensadas desde o primeiro pixel até a última chamada de API.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Não é só "quem desenha + quem programa"&lt;/strong&gt;&lt;br&gt;
É quem pensa em &lt;strong&gt;&lt;em&gt;experiência e implementação juntas&lt;/em&gt;&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Um botão que não só parece bem-desenhado, mas também considera estados de loading, erro, disabled.&lt;/li&gt;
&lt;li&gt;Um fluxo de cadastro que não só é bonito, mas que já prevê o que o backend vai precisar para validar, salvar e devolver feedback.&lt;/li&gt;
&lt;li&gt;Um produto que pensa em performance, acessibilidade e usabilidade em uma única conversa.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Como front-end e UX mudam sua forma de ver produto
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Como eu via a tela como desenvolvedor front-end&lt;/strong&gt;&lt;br&gt;
HTML estruturando a informação. CSS dando forma. Um pouco de JavaScript dando comportamento. A tela era resultado direto do código.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;O que mudou quando migrei para UX/UI&lt;/strong&gt;&lt;br&gt;
Passei a olhar para o produto como um todo: pesquisa, fluxos, contexto do usuário. O código virou uma consequência, não o ponto de partida.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;O que muda agora é que percebo que não preciso mais ficar só de um lado&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Por que estou voltando ao código agora
&lt;/h2&gt;

&lt;p&gt;Com o avanço de ferramentas como IA integrada ao Figma, prototipagem cada vez mais próxima do código e experiências acumuladas como Product Designer, foi aí que o conceito de Design Engineer passou a fazer sentido de verdade pra mim.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JavaScript moderno, React e um pouco de backend&lt;/strong&gt;&lt;br&gt;
Comecei a estudar de forma mais focada para:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Atualizar meu JavaScript (ES2024, async/await, fetch, arrays/objetos)&lt;/li&gt;
&lt;li&gt;Reativar React (componentes, hooks, estado compartilhado)&lt;/li&gt;
&lt;li&gt;Entender backend leve (Node/Express, rotas simples, persistência básica)&lt;/li&gt;
&lt;li&gt;Pensar em performance e otimização das interfaces que ajudo a construir&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Como estou usando IA no processo de aprendizado
&lt;/h3&gt;

&lt;p&gt;Hoje, uso o Claude AI não como substituto, mas como apoio para:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Quebrar problemas de código em etapas menores&lt;/li&gt;
&lt;li&gt;Revisar fluxos de dados entre front e backend&lt;/li&gt;
&lt;li&gt;Organizar pensamentos e lógica de features&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Design Engineer não é um cargo. É uma forma de pensar
&lt;/h3&gt;

&lt;p&gt;O que mais gosto de dizer é que, em 2026, &lt;em&gt;&lt;strong&gt;Design Engineer não é só um título de empresa grande&lt;/strong&gt;&lt;/em&gt; ou de time específico. Pode existir em qualquer lugar onde UX e código se encontram:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Em um produto próprio&lt;/li&gt;
&lt;li&gt;Em um projeto freelancer&lt;/li&gt;
&lt;li&gt;Em um repositório público&lt;/li&gt;
&lt;li&gt;Em um fluxo de trabalho híbrido, mesmo sem ter um cargo oficial com esse nome&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;O que importa é a &lt;strong&gt;maneira de pensar&lt;/strong&gt;: UX + código ao mesmo tempo. Protótipos e implementação como parte do mesmo processo. Interfaces que consideram o que o usuário sente e o que o backend precisa.&lt;/p&gt;

&lt;h2&gt;
  
  
  O que vem por aí: UX data-driven e componentes React
&lt;/h2&gt;

&lt;p&gt;Esse movimento não é só "voltar" ao front-end. É re-aprender JavaScript, entender melhor React e backend, e levar essa visão de UX para dentro do código.&lt;br&gt;
Em breve, quero escrever mais sobre:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Como transformo um fluxo de UX do Figma em componentes React&lt;/li&gt;
&lt;li&gt;Como penso em performance e carregamento em UX para web&lt;/li&gt;
&lt;li&gt;Como organizar estudos de UX + código em ciclos curtos e práticos&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;E numa parte que ainda não me sinto confortável, mas sei que é essencial: vou falar sobre como estou começando a me tornar uma &lt;strong&gt;UX mais data-driven&lt;/strong&gt;, porque ser Design Engineer em 2026 também é aprender a ouvir o que os números dizem sobre o UX que eu desenho.&lt;/p&gt;

&lt;h2&gt;
  
  
  Você também está nesse meio-termo entre UX e código?
&lt;/h2&gt;

&lt;p&gt;Se você chegou até aqui, é bem provável que também se sinta em algum meio-termo entre UX e código.&lt;/p&gt;

&lt;p&gt;Que tal comentar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Como você se vê hoje entre design e desenvolvimento?&lt;/li&gt;
&lt;li&gt;Você já tentou voltar pro código depois de virar UX, ou está no caminho invertido?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Esse é o tipo de conversa que me ajuda a entender como o conceito de &lt;strong&gt;Design Engineer&lt;/strong&gt; está se moldando, bem além de títulos de empresa.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>design</category>
      <category>ux</category>
      <category>braziliandevs</category>
    </item>
    <item>
      <title>Construí um gerador de playlists no Spotify com Claude</title>
      <dc:creator>Leo Garcez</dc:creator>
      <pubDate>Tue, 24 Mar 2026 02:01:08 +0000</pubDate>
      <link>https://dev.to/he4rt/construi-um-gerador-de-playlists-no-spotify-com-claude-18ge</link>
      <guid>https://dev.to/he4rt/construi-um-gerador-de-playlists-no-spotify-com-claude-18ge</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Eu queria digitar &lt;em&gt;“noite chuvosa, meio melancólica”&lt;/em&gt; e receber uma playlist perfeita. Então eu construí isso.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Eu construí um gerador de playlists com IA usando &lt;strong&gt;Claude + Spotify API&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Você descreve um humor → ele gera 50 músicas → salva direto no Spotify&lt;/li&gt;
&lt;li&gt;O maior problema foi OAuth local com NextAuth (sim, foi um inferno)&lt;/li&gt;
&lt;li&gt;Claude funciona bem, mas precisa de bastante controle pra não inventar músicas&lt;/li&gt;
&lt;li&gt;Streaming com SSE melhorou muito a UX&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://moodify.com.br" rel="noopener noreferrer"&gt;Demo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/LeoGarcez/moodify" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://open.spotify.com/playlist/0L2bStHbYyfiGJFZfB1CDO?si=a93194d73eec484c" rel="noopener noreferrer"&gt;Playlist de exemplo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Índice
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A Ideia&lt;/li&gt;
&lt;li&gt;A Stack&lt;/li&gt;
&lt;li&gt;O Problema de Trabalhar com OAuth Localmente&lt;/li&gt;
&lt;li&gt;Endpoints Deprecated do Spotify&lt;/li&gt;
&lt;li&gt;Spotify em Produção&lt;/li&gt;
&lt;li&gt;Fazendo o Claude Obedecer&lt;/li&gt;
&lt;li&gt;Prompt Engineering Anti-Alucinação&lt;/li&gt;
&lt;li&gt;Modo Related Artists&lt;/li&gt;
&lt;li&gt;Construindo o Perfil Musical&lt;/li&gt;
&lt;li&gt;Streaming com SSE&lt;/li&gt;
&lt;li&gt;O Que Aprendi&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A Ideia
&lt;/h2&gt;

&lt;p&gt;Fazer um gerador de playlists que realmente &lt;em&gt;entendesse&lt;/em&gt; vibes, não só tags de gênero. Algo tipo: você digita &lt;strong&gt;"tarde fria num apartamento vazio"&lt;/strong&gt; e recebe uma playlist boa de verdade, já salva no seu Spotify.&lt;/p&gt;

&lt;p&gt;O conceito:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Usuário descreve um humor&lt;/li&gt;
&lt;li&gt;Claude retorna 50 músicas em JSON&lt;/li&gt;
&lt;li&gt;App busca cada faixa no Spotify&lt;/li&gt;
&lt;li&gt;Cria e salva a playlist na conta do usuário&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Três APIs, um app.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Stack
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Next.js 14 (App Router)
NextAuth v5 beta
Anthropic Claude API (claude-sonnet-4-6)
Spotify Web API
Supabase (PostgreSQL)
TypeScript + Tailwind CSS + Framer Motion
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Escolhi o Claude porque ele tá bem em alta agora e, na prática, é confiável, alucina menos do que eu esperava pra esse tipo de tarefa. Ele é um pouco menos criativo que o GPT nas recomendações, mas compensa sendo mais previsível no formato das respostas, o que importa bastante quando você tá parseando JSON.&lt;/p&gt;

&lt;h2&gt;
  
  
  O Problema de Trabalhar com OAuth Localmente
&lt;/h2&gt;

&lt;p&gt;Isso me custou algumas horas e sessões de debug. Vou detalhar porque tem várias camadas de problema e você provavelmente vai bater na mesma parede se estiver usando NextAuth v5 com Spotify.&lt;/p&gt;

&lt;h3&gt;
  
  
  O Spotify não aceita &lt;code&gt;localhost&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;O Spotify &lt;a href="https://developer.spotify.com/documentation/web-api/concepts/redirect_uri" rel="noopener noreferrer"&gt;proíbe &lt;code&gt;localhost&lt;/code&gt; como redirect URI&lt;/a&gt; pra URIs de loopback. A solução é usar &lt;code&gt;127.0.0.1&lt;/code&gt;. Cadastrei &lt;code&gt;http://127.0.0.1:3000/api/auth/callback/spotify&lt;/code&gt; no dashboard e setei &lt;code&gt;AUTH_URL=http://127.0.0.1:3000&lt;/code&gt; no &lt;code&gt;.env.local&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Não foi suficiente.&lt;/p&gt;

&lt;h3&gt;
  
  
  O &lt;code&gt;NextRequest&lt;/code&gt; normaliza URLs pra &lt;code&gt;localhost&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;O Next.js, independente do host que você passa pra &lt;code&gt;next dev -H&lt;/code&gt;, normaliza &lt;code&gt;req.url&lt;/code&gt; e &lt;code&gt;req.nextUrl.href&lt;/code&gt; de volta pra &lt;code&gt;localhost&lt;/code&gt; em desenvolvimento. Isso não é bug documentado — é comportamento interno do framework.&lt;/p&gt;

&lt;p&gt;O NextAuth v5 tem um utilitário chamado &lt;code&gt;reqWithEnvURL&lt;/code&gt; que tenta corrigir exatamente isso, mas falha silenciosamente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Dentro do next-auth — simplificado&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;reqWithEnvURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AUTH_URL&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ← o construtor normaliza de volta pra localhost&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mesmo passando &lt;code&gt;127.0.0.1&lt;/code&gt; explicitamente, o construtor do &lt;code&gt;NextRequest&lt;/code&gt; sobrescreve. A "correção" não funciona.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dois momentos onde o redirect URI importa
&lt;/h3&gt;

&lt;p&gt;O OAuth tem &lt;strong&gt;dois&lt;/strong&gt; momentos distintos onde o redirect URI aparece:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Requisição de autorização&lt;/strong&gt; — a URL do Spotify onde o usuário loga. O &lt;code&gt;redirect_uri&lt;/code&gt; aqui vem dos seus params de configuração, então você pode hardcodar &lt;code&gt;127.0.0.1&lt;/code&gt; na config do provider. Isso funcionou.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Troca de token&lt;/strong&gt; — quando o Spotify manda o código de volta, o &lt;code&gt;@auth/core&lt;/code&gt; envia um POST pra trocar o código por tokens. O &lt;code&gt;redirect_uri&lt;/code&gt; nessa requisição vem de &lt;code&gt;provider.callbackUrl&lt;/code&gt;, que é derivado de &lt;code&gt;params.url.origin&lt;/code&gt; — ou seja, da &lt;strong&gt;URL da requisição de callback&lt;/strong&gt;. Se essa URL ainda diz &lt;code&gt;localhost&lt;/code&gt;, a troca falha com &lt;code&gt;invalid_grant: Invalid redirect URI&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;O sintoma era desconcertante: a URL de autorização mostrava &lt;code&gt;127.0.0.1&lt;/code&gt; corretamente, mas a troca de token continuava falhando. Fui atrás do código do &lt;code&gt;@auth/core&lt;/code&gt; pra entender o que tava acontecendo.&lt;/p&gt;

&lt;h3&gt;
  
  
  A solução: &lt;code&gt;Auth()&lt;/code&gt; direto com &lt;code&gt;Request&lt;/code&gt; nativo
&lt;/h3&gt;

&lt;p&gt;Objetos &lt;code&gt;Request&lt;/code&gt; nativos do browser/Node &lt;strong&gt;não normalizam URLs&lt;/strong&gt;. A correção é contornar os route handlers do NextAuth e chamar &lt;code&gt;Auth()&lt;/code&gt; do &lt;code&gt;@auth/core&lt;/code&gt; diretamente, passando um &lt;code&gt;Request&lt;/code&gt; nativo com a URL já corrigida:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/app/api/auth/[...nextauth]/route.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Auth&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@auth/core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;authConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../../../../../auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authOrigin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AUTH_URL&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s2"&gt;`http://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;host&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fixedUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^https&lt;/span&gt;&lt;span class="se"&gt;?&lt;/span&gt;&lt;span class="sr"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\/\/[^/]&lt;/span&gt;&lt;span class="sr"&gt;+/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;authOrigin&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hasBody&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HEAD&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fixedUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;hasBody&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// duplex necessário para streaming de body no Node.js&lt;/span&gt;
    &lt;span class="p"&gt;...(&lt;/span&gt;&lt;span class="nx"&gt;hasBody&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;duplex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;half&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;object&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nc"&gt;Auth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;buildRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;authConfig&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Parameters&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;Auth&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;POST&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Versão do &lt;code&gt;@auth/core&lt;/code&gt;:&lt;/strong&gt; ao usar &lt;code&gt;Auth()&lt;/code&gt; diretamente, instale a versão exata que o &lt;code&gt;next-auth&lt;/code&gt; usa internamente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;ls&lt;/span&gt; @auth/core  &lt;span class="c"&gt;# veja qual versão o next-auth requer&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; @auth/core@0.41.0 &lt;span class="nt"&gt;--save-exact&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Versões diferentes criam conflitos de tipo que explodem em runtime.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Configuração explícita no &lt;code&gt;auth.ts&lt;/code&gt;:&lt;/strong&gt; ao contornar os handlers do NextAuth, &lt;code&gt;setEnvDefaults&lt;/code&gt; não roda mais. Configure &lt;code&gt;basePath&lt;/code&gt;, &lt;code&gt;secret&lt;/code&gt; e &lt;code&gt;redirect_uri&lt;/code&gt; explicitamente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// auth.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextAuthConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;trustHost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;basePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AUTH_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nc"&gt;Spotify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AUTH_SPOTIFY_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;clientSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AUTH_SPOTIFY_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://accounts.spotify.com/authorize&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SPOTIFY_SCOPES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;show_dialog&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;redirect_uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AUTH_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/api/auth/callback/spotify`&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;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="c1"&gt;// ... callbacks e pages como antes&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Bônus: problema de domínio do cookie PKCE
&lt;/h3&gt;

&lt;p&gt;Mesmo com tudo acima, pode acontecer mais uma falha: se o usuário acessa &lt;code&gt;http://localhost:3000&lt;/code&gt;, o navegador seta o cookie PKCE pro domínio &lt;code&gt;localhost&lt;/code&gt;. Quando o Spotify redireciona de volta pra &lt;code&gt;http://127.0.0.1:3000/...&lt;/code&gt;, o navegador não envia o cookie — domínios diferentes — e o &lt;code&gt;code_verifier&lt;/code&gt; some. A troca falha de novo.&lt;/p&gt;

&lt;p&gt;A correção é garantir que &lt;code&gt;localhost:3000&lt;/code&gt; nunca apareça pro usuário, redirecionando via &lt;code&gt;next.config.mjs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;redirects&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/:path*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;has&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;host&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;localhost:3000&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
      &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://127.0.0.1:3000/:path*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;permanent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&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="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;has&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;host&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;localhost:3000&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
      &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://127.0.0.1:3000/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;permanent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&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;Com isso, todo o fluxo de auth fica em &lt;code&gt;127.0.0.1&lt;/code&gt; e os cookies PKCE chegam onde precisam chegar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Endpoints Deprecated do Spotify
&lt;/h2&gt;

&lt;p&gt;Com o auth funcionando, bati num muro de 403s.&lt;/p&gt;

&lt;p&gt;O Spotify deprecated alguns endpoints sem muito alarde:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Antigo (deprecated)&lt;/th&gt;
&lt;th&gt;Novo&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;POST /playlists/{id}/tracks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;POST /playlists/{id}/items&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GET /playlists/{id}/tracks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GET /playlists/{id}/items&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;GET /audio-features&lt;/code&gt;, &lt;code&gt;GET /recommendations&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Deprecated, sem substituto&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A migração &lt;code&gt;/tracks&lt;/code&gt; → &lt;code&gt;/items&lt;/code&gt; está documentada, mas é fácil de perder se você seguiu um tutorial de 2022. Audio features e recomendações sumindo foi mais chato — tive que construir contexto de energia de outra forma, mais sobre isso abaixo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Spotify em Produção
&lt;/h2&gt;

&lt;p&gt;Tem uma limitação que eu não vi muito discutida: no modo de desenvolvimento, o Spotify permite apenas &lt;strong&gt;5 usuários autenticados&lt;/strong&gt; E eles precisam ser adicionados manualmente via allowlist no dashboard.&lt;/p&gt;

&lt;p&gt;Pra ir além disso, você precisa solicitar Extended Quota Mode. E o processo atual é bem pesado:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Entidade jurídica registrada (pessoa física não é aceita desde maio de 2025)&lt;/li&gt;
&lt;li&gt;Serviço já lançado e ativo&lt;/li&gt;
&lt;li&gt;Mínimo de &lt;strong&gt;250.000 usuários ativos mensais&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Viabilidade comercial&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A análise pode levar até seis semanas, e sem garantia de aprovação.&lt;/p&gt;

&lt;p&gt;Na prática, isso significa que se você tá construindo algo novo como indie dev, vai ficar travado em modo de desenvolvimento. Você consegue testar e mostrar pra até 4 pessoas além de você — e só. Vale saber disso antes de planejar algum lançamento público.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fazendo o Claude Obedecer
&lt;/h2&gt;

&lt;p&gt;O system prompt instrui o Claude a retornar &lt;strong&gt;apenas&lt;/strong&gt; um array JSON, sem markdown, sem explicações. Essa parte é direta. O problema mais difícil é conseguir 50 faixas &lt;em&gt;que realmente existam&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  One-shot prompting
&lt;/h3&gt;

&lt;p&gt;Incluir uma conversa de exemplo completa (usuário + assistente) antes do request real reduziu bastante os erros de formato, especialmente com artistas não-ingleses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;messages&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="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Create a playlist for this mood/vibe: late night drive, nostalgic&lt;/span&gt;&lt;span class="dl"&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;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;assistant&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Drive&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;artist&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The Cars&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Synth-pop clássico com energia perfeita de madrugada&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Running Up That Hill&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;artist&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Kate Bush&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Art-pop etéreo, emocionalmente assombroso&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="c1"&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;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;actualUserMessage&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;h3&gt;
  
  
  Extração de JSON como fallback
&lt;/h3&gt;

&lt;p&gt;Mesmo com prompting cuidadoso, modelos eventualmente jogam texto de introdução. Sempre extraia o array:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\[[\s\S]&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;\]&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Nenhum array JSON encontrado&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;suggestions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;match&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Prompt Engineering Anti-Alucinação
&lt;/h2&gt;

&lt;p&gt;Descobri que &lt;strong&gt;quanto mais músicas você pede, mais o modelo inventa títulos&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Pedindo 70 músicas, o Claude começa a criar faixas com nomes plausíveis que não existem. Com 50 e restrições explícitas, fica bem melhor.&lt;/p&gt;

&lt;p&gt;O que adicionei ao system prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EXISTÊNCIA NO SPOTIFY — CRÍTICO:
Cada música DEVE existir no Spotify. Antes de incluir uma faixa, pergunte:
"Tenho certeza que esta música existe no Spotify com este título e artista exatos?"
Se houver qualquer dúvida, escolha outra música que você tem certeza.

REGRAS DE FORMATO DO TÍTULO:
- Use apenas o título canônico limpo do lançamento
- SEM sufixos: sem "- Remastered", "- Live at...", "- Radio Edit"
- Use o nome do lançamento mais conhecido, não compilações

ARMADILHAS COMUNS DE ALUCINAÇÃO:
- Não invente títulos de músicas que parecem plausíveis mas podem não existir
- Não confunda dois artistas com nomes similares
- Não sugira deep cuts que você não tem certeza
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Também removi a abordagem de duas etapas "gerar 70, refinar pra 50". Era cara em tempo e custo, e uma única geração de 50 com boas instruções performa melhor.&lt;/p&gt;

&lt;h3&gt;
  
  
  O system prompt atual completo
&lt;/h3&gt;

&lt;p&gt;Esse é o prompt que tá rodando em produção hoje:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are a world-class Spotify playlist curator.

GOAL:
Generate EXACTLY 50 high-quality songs for a playlist.

OUTPUT:
Return ONLY a raw JSON array. No markdown, no explanations.

Each item:
- "title": string — the canonical Spotify title, nothing else
- "artist": string — the primary artist exactly as listed on Spotify
- "reason": string — max 10 words

SPOTIFY EXISTENCE — CRITICAL:
Every song MUST exist on Spotify. Before including a track, ask yourself:
"Am I certain this song exists on Spotify under this exact title and artist?"
If there is any doubt, pick a different song you are certain about.

TITLE FORMAT RULES:
- Use the clean, canonical release title only
- NO suffixes: no "- Remastered", "- Live at...", "- Radio Edit", "- feat. X"
- NO parentheticals unless part of the official title
- Use the most well-known release name, not compilations or bonus versions

COMMON HALLUCINATION TRAPS TO AVOID:
- Do not invent song titles that sound plausible but may not exist
- Do not confuse two artists with similar names
- Do not suggest deep cuts you are uncertain about
- Do not suggest songs only released in specific regions unavailable globally

DISTRIBUTION:
- 50% recognizable hits (high confidence they exist)
- 40% lesser-known but confirmed tracks
- 10% deep cuts you are fully certain about

DIVERSITY:
- At least 2 genres
- At least 3 decades
- Non-English tracks welcome if you are certain they are on Spotify

CURATION:
- Cohesive flow, playlist-worthy, non-random
- No duplicates

Return ONLY the JSON array. Exactly 50 items.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Curiosidade: o prompt tá em inglês mesmo que o usuário escreva em português. O Claude entende o humor no idioma que vier e retorna os dados no formato esperado sem problema.&lt;/p&gt;

&lt;h3&gt;
  
  
  Melhor resolução de busca no Spotify
&lt;/h3&gt;

&lt;p&gt;Mesmo com um bom prompt, algumas faixas voltam com títulos ou artistas levemente errados. Três ajustes no lado da busca:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Buscar 10 candidatos em vez de 1&lt;/strong&gt;&lt;br&gt;
Em vez de &lt;code&gt;limit=1&lt;/code&gt;, buscar &lt;code&gt;limit=10&lt;/code&gt; e escolher o melhor match.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Filtro de popularidade&lt;/strong&gt;&lt;br&gt;
Pular resultados com &lt;code&gt;popularity &amp;lt; 30&lt;/code&gt; — evita gravar versões ao vivo obscuras quando a faixa correta não é encontrada:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;POPULARITY_FLOOR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;aboveFloor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;candidates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;popularity&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;POPULARITY_FLOOR&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aboveFloor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;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;aboveFloor&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;candidates&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Fuzzy matching + seleção por popularidade&lt;/strong&gt;&lt;br&gt;
Normalizar strings e verificar correspondência bidirecional de substring, depois escolher o match com maior popularidade:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fuzzyMatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SpotifyTrack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;suggestion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ClaudeTrackSuggestion&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;trackName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;normalizeStr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;candidate&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;artistName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;normalizeStr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;artists&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;name&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sugTitle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;normalizeStr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;suggestion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sugArtist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;normalizeStr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;suggestion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;artist&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;titleMatch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;trackName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sugTitle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;sugTitle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;trackName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;artistMatch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;artistName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sugArtist&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;sugArtist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;artistName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;titleMatch&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;artistMatch&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;
  
  
  Modo Related Artists
&lt;/h2&gt;

&lt;p&gt;Playlists geradas por IA alucinam mais quando o humor é específico de artista — tipo "algo como Radiohead". Nesses casos, o grafo de artistas do próprio Spotify é mais confiável que o Claude.&lt;/p&gt;

&lt;p&gt;Adicionei detecção automática: uma chamada rápida ao Claude Haiku (~0,5s) classifica o prompt antes da geração principal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Retorna { mode: "ai" | "related", artists: ["Radiohead", "Nick Cave"] }&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;detected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;detectPlaylistMode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mood&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Se artistas são detectados, o app:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Os resolve no Spotify&lt;/li&gt;
&lt;li&gt;Busca artistas relacionados (&lt;code&gt;GET /artists/{id}/related-artists&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Pega top tracks de cada artista relacionado&lt;/li&gt;
&lt;li&gt;Monta uma playlist com dados reais do Spotify, sem depender do Claude pra nada&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Se não detectar artistas, cai pro fluxo normal com Claude.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;detected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;related&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;detected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;artists&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;suggestions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resolvedSeeds&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;buildRelatedArtistsPlaylist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;detected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;artists&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;energy&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;suggestions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;suggestions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;related&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;seedArtists&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;resolvedSeeds&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// fallthrough para modo AI se não encontrou nada&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Construindo o Perfil Musical
&lt;/h2&gt;

&lt;p&gt;O app deixa usuários escolher uma playlist de referência ou seus top artistas do Spotify (último mês / 6 meses / histórico completo). Esse contexto é passado pro Claude como uma impressão digital musical.&lt;/p&gt;

&lt;p&gt;Mandar nomes de faixas brutos confunde o modelo. Em vez disso, agregue num perfil:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Contar frequência de artistas em todas as faixas da playlist&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;artistFreq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;track&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;tracks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;track&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;artists&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;artistFreq&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="nx"&gt;a&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="nx"&gt;artistFreq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;a&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="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pra playlists, também busco dados de gênero dos artistas principais via &lt;code&gt;GET /artists/{id}&lt;/code&gt; (5 chamadas paralelas), já que os itens de playlist não retornam gêneros nativamente.&lt;/p&gt;

&lt;p&gt;Mesmo sem seleção de referência explícita, o app passa os top artistas e gêneros baseline do usuário como contexto suave pra cada geração.&lt;/p&gt;

&lt;p&gt;A instrução no prompt mudou de "não recomende esses artistas" pra algo mais útil:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;→ Biase as recomendações em direção a este DNA musical: tempo, humor e estilo de produção similar.
→ Descubra artistas com som SIMILAR — não necessariamente os mesmos artistas.
→ NÃO repita faixas já listadas acima.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Streaming com SSE
&lt;/h2&gt;

&lt;p&gt;O fluxo original: buscar todas as 50 faixas em paralelo, retornar tudo de uma vez, mostrar um spinner.&lt;/p&gt;

&lt;p&gt;O problema é que o usuário ficava olhando "Salvando no Spotify..." por 5-10 segundos sem nenhum feedback. Server-Sent Events resolve isso — cada faixa é emitida conforme resolve:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Na rota da API&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ReadableStream&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;send&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enqueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;encoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`data: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&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="s2"&gt;\n\n`&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;suggestions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;suggestion&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;track&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;searchTrack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;suggestion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;track&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;foundTracks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;track&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;suggestion&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
          &lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;track&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;track&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;suggestion&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// emitido imediatamente&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="c1"&gt;// Criar playlist depois que todas as buscas terminam&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;playlist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createPlaylist&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt;
    &lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;done&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;playlist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;found&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;foundTracks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&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="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text/event-stream&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Cache-Control&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;no-cache&lt;/span&gt;&lt;span class="dl"&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;A busca paralela continua na velocidade máxima. Mas agora o cliente vê cada faixa aparecer com album art conforme resolve, com uma barra de progresso ao vivo — em vez de um spinner em branco por 10 segundos.&lt;/p&gt;

&lt;h2&gt;
  
  
  O Que Aprendi
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Sobre prompt engineering:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Menos é mais (50 &amp;gt; 70)&lt;/li&gt;
&lt;li&gt;Exemplos one-shot (par de mensagens usuário + assistente) são mais confiáveis que instruções de formato detalhadas.&lt;/li&gt;
&lt;li&gt;Dizer o que não fazer funciona muito bem&lt;/li&gt;
&lt;li&gt;Contexto de perfil funciona melhor como guia de DNA musical, não como lista de restrições.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Sobre a API do Spotify:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sempre verifique se o endpoint ainda é atual. &lt;code&gt;/tracks&lt;/code&gt; → &lt;code&gt;/items&lt;/code&gt;, audio features sumiu, recomendações sumiram.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /artists/{id}/related-artists&lt;/code&gt; funciona bem pra descoberta e quase ninguém usa.&lt;/li&gt;
&lt;li&gt;O score de popularidade nas faixas é um bom proxy pra "essa faixa existe como esperado."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Sobre streaming no Next.js:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ReadableStream&lt;/code&gt; + &lt;code&gt;text/event-stream&lt;/code&gt; funciona limpo no App Router.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Promise.all&lt;/code&gt; + emit-on-resolve te dá paralelismo real com UI progressiva de graça.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Sobre Next.js + OAuth:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;NextRequest&lt;/code&gt; normaliza URLs pra &lt;code&gt;localhost&lt;/code&gt; em desenvolvimento, mesmo que você passe &lt;code&gt;127.0.0.1&lt;/code&gt; explicitamente. Use &lt;code&gt;Request&lt;/code&gt; nativo quando a URL importa.&lt;/li&gt;
&lt;li&gt;O NextAuth v5 tem um utilitário &lt;code&gt;reqWithEnvURL&lt;/code&gt; que tenta corrigir isso mas usa &lt;code&gt;new NextRequest()&lt;/code&gt; internamente — que normaliza de novo. A correção do framework não funciona.&lt;/li&gt;
&lt;li&gt;O OAuth tem &lt;strong&gt;dois&lt;/strong&gt; momentos onde o &lt;code&gt;redirect_uri&lt;/code&gt; é verificado: na requisição de autorização e na troca de token. Você precisa garantir que os dois mostrem &lt;code&gt;127.0.0.1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;provider.callbackUrl&lt;/code&gt; é derivado da URL da requisição de callback — não da sua config. Se a URL da requisição ainda diz &lt;code&gt;localhost&lt;/code&gt;, a troca falha com &lt;code&gt;invalid_grant&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Ao usar &lt;code&gt;Auth()&lt;/code&gt; do &lt;code&gt;@auth/core&lt;/code&gt; diretamente, pin a versão exata que o &lt;code&gt;next-auth&lt;/code&gt; requer. Versões diferentes causam conflitos de tipo em runtime.&lt;/li&gt;
&lt;li&gt;Cookies PKCE são scopados por domínio. Se o usuário começa em &lt;code&gt;localhost&lt;/code&gt; e o callback chega em &lt;code&gt;127.0.0.1&lt;/code&gt;, o &lt;code&gt;code_verifier&lt;/code&gt; some. Redirecione todo o tráfego de &lt;code&gt;localhost&lt;/code&gt; pra &lt;code&gt;127.0.0.1&lt;/code&gt; no &lt;code&gt;next.config.mjs&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;NextAuth v5 é poderoso mas a documentação beta é bem escassa. Ler o código-fonte do &lt;code&gt;@auth/core&lt;/code&gt; foi necessário pra entender o que estava acontecendo.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Teste Você Mesmo
&lt;/h2&gt;

&lt;p&gt;O app se chama &lt;strong&gt;Moodify&lt;/strong&gt;. Está OpenSource no &lt;a href="https://github.com/LeoGarcez/moodify" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. Se você tá fazendo algo parecido, espero que ajude um pouco.&lt;/p&gt;




&lt;p&gt;Como vocês melhorariam esse prompt? Se alguém já passou por algo parecido ou tiver ideias, comenta aí&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Construído com Next.js, Claude API, Spotify Web API e Supabase. Deploy no Vercel.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>nextjs</category>
      <category>braziliandevs</category>
    </item>
    <item>
      <title>Engenharia de Prompt: Por Que a Forma Como Você Pergunta Muda Tudo(Um guia introdutório)</title>
      <dc:creator>Fran Borges</dc:creator>
      <pubDate>Mon, 23 Mar 2026 16:20:34 +0000</pubDate>
      <link>https://dev.to/he4rt/engenharia-de-prompt-por-que-a-forma-como-voce-pergunta-muda-tudoum-guia-introdutorio-3hb0</link>
      <guid>https://dev.to/he4rt/engenharia-de-prompt-por-que-a-forma-como-voce-pergunta-muda-tudoum-guia-introdutorio-3hb0</guid>
      <description>&lt;p&gt;Neste artigo irei explicar alguns pontos importantes sobre Engenharia de prompt, e como saber esses pontos pode te ajudar muito no dia a dia lidando com IAs, no seus estudos, pesquisas, trabalho, vibeconding, whatever.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prefácio&lt;/li&gt;
&lt;li&gt;Antes de tudo, para lembrar, o que é uma LLM mesmo?&lt;/li&gt;
&lt;li&gt;
Fazendo Perguntas

&lt;ul&gt;
&lt;li&gt;1 - Evite a Ambiguidade&lt;/li&gt;
&lt;li&gt;2 - Delimite o Escopo&lt;/li&gt;
&lt;li&gt;3 - Forneça Contexto Relevante&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Considerações Finais&lt;/li&gt;

&lt;/ul&gt;

&lt;h1&gt;
  
  
  Prefácio
&lt;/h1&gt;

&lt;p&gt;A base do conhecimento é necessária em tudo que se quer aprender, com LLMs não é diferente. Analisando padrões, conversando com amigos, observei que a grande parte das pessoas que usam LLMs no dia a dia de trabalho, estudo, pessoas que trabalham com tecnologia e principalmente quem está começando na área tech, não tem ideia de como a LLM funciona e de como fazer as perguntas e tirar dúvidas de forma certa para um ChatGPT da vida.&lt;/p&gt;

&lt;p&gt;Tendo isso em vista, e como venho estudando bastante sobre esse assunto, como forma de compartilhar o conhecimento, já dizia a poetisa brasileira Cora Coralina: &lt;em&gt;"Feliz aquele que transfere o que sabe, e aprende o que ensina"&lt;/em&gt;, farei uma série de artigos explicando sobre o tema, e esse é só o primeiro deles...&lt;/p&gt;

&lt;h1&gt;
  
  
  Antes de tudo, para lembrar, o que é uma LLM mesmo?
&lt;/h1&gt;

&lt;p&gt;A LLM pode ser definida de algumas formas. Uma delas é: &lt;em&gt;"São modelos de linguagem de máquina, que usam algoritmos de aprendizado profundo (Deep Learning) para processar e aprender a linguagem natural"&lt;/em&gt;, essa é a sua definição estrutural. A definição que mais gosto é: &lt;em&gt;"A LLM, na sua essência, é composta por dois arquivos: um contendo os pesos (parâmetros) com os conhecimentos aprendidos, e o outro com o código necessário para rodar os dados aprendidos."&lt;/em&gt;, como uma pessoa visual, consigo imaginar melhor como funciona.&lt;/p&gt;

&lt;p&gt;Então, ChatGPT, Claude e muitos outros são exatamente isso, e atualmente têm a capacidade de executar várias atividades, como escrever código, traduzir texto, responder às mais variadas dúvidas e, dependendo da sua capacidade de escrever prompts mais robustos, pode até criar arquiteturas de produtos, te ajudar a resolver bugs complexos, te dar ideias, e até criar um SaaS revolucionário &lt;em&gt;(risos)&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;O ponto é que a capacidade das LLMs de "compreender" e "criar" chegou a um ponto bem avançado, e você só chega na camada -17 de boas respostas fazendo as perguntas de forma certa!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nota:&lt;/strong&gt; &lt;em&gt;Camada -17&lt;/em&gt; se refere à camada onde se acha o minério de ouro no jogo Minecraft ;). &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Fazendo Perguntas
&lt;/h1&gt;

&lt;p&gt;Como fazer as perguntas certas? E por que saber isso importa? Vamos lá.&lt;/p&gt;

&lt;p&gt;Saber fazer as perguntas certas para uma LLM é tão importante que existe uma área da tecnologia específica só para isso: a &lt;strong&gt;Engenharia de Prompts&lt;/strong&gt;, definida como &lt;em&gt;"a ciência empírica de planejar, criar e testar prompts para gerar melhores respostas em LLMs"&lt;/em&gt;. Saber fazer as perguntas certas te coloca em outro nível, você consegue obter as melhores respostas, e isso te traz muitos ganhos, sendo o principal deles a &lt;strong&gt;produtividade&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mas afinal, como perguntar de forma certa?
&lt;/h2&gt;

&lt;p&gt;A LLM é poderosa, mas não adivinha sua intenção. Para obter os melhores resultados nas suas buscas, você precisa se concentrar na criação de prompts claros, na especificidade e ser rico em dar contexto. E tudo isso envolve:&lt;/p&gt;

&lt;h3&gt;
  
  
  1 - Evite a Ambiguidade
&lt;/h3&gt;

&lt;p&gt;"Espaço de possibilidades"&lt;/p&gt;

&lt;p&gt;Quando você escreve um prompt muito ambíguo, vago, o modelo enxerga vários caminhos estatisticamente válidos. É como se ele estivesse numa encruzilhada com 50 estradas e todas tivessem placas dizendo &lt;em&gt;"Talvez por aqui"&lt;/em&gt;, ele vai escolher uma, mas não necessariamente a que você precisa: a estrada com o percurso mais rápido e sem trânsito (a resposta correta de fato).&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Prompt ambíguo:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Crie uma API."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;O que a LLM "vê": Vários caminhos, API REST? GraphQL? Em qual linguagem? Para qual domínio? Com autenticação? Com banco? O modelo vai escolher o caminho mais estatisticamente comum nos dados de treino (provavelmente uma API REST genérica em Node.js com Express), que pode não ter nada a ver com o que você precisa.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Prompt sem ambiguidade:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Crie um microsserviço em Node.js com Express e TypeScript para processar pagamentos via
Stripe. Endpoints: criar pagamento, confirmar webhook e consultar status. O payload tem:
orderId(UUID), amount(number), currency(enum: BRL, USD) e customerId(string).
Use zod para validação, Prisma com PostgreSQL para persistir as transações e winston
para logs. Retorne status codes apropriados como(201, 200, 400, 422, 500). Trate falhas de
rede com retry automático (máx. 3 tentativas)."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;O que a LLM "vê": Um caminho quase único. Cada detalhe funciona como uma restrição que elimina ambiguidade: "Node.js com Express e TypeScript" define runtime, framework e linguagem de uma vez. "Pagamentos via Stripe" restringe o SDK e o domínio. "3 endpoints explícitos + payload com tipos" elimina adivinhações sobre rotas e schema. "Zod, Prisma, PostgreSQL, Winston" travam a stack, o modelo não vai sugerir alternativas. "Status codes específicos + retry com máximo de 3 tentativas" definem os status HTTP e a estratégia com limites claros. A distribuição de probabilidade fica concentrada e o modelo praticamente "só tem uma opção" a cada token gerado. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  2 - Delimite o Escopo
&lt;/h3&gt;

&lt;p&gt;"Janela de atenção"&lt;/p&gt;

&lt;p&gt;LLMs têm um &lt;em&gt;context window&lt;/em&gt;(Janela), uma quantidade de tokens (pedaço de palavra) que conseguem ser processados de uma vez. Isso inclui o seu prompt e a resposta gerada. Dentro dessa janela, existe um fenômeno importante: nem todos os tokens recebem a mesma "atenção" no processamento.&lt;/p&gt;

&lt;p&gt;O mecanismo de &lt;em&gt;self-attention&lt;/em&gt; (o coração da arquitetura Transformer, não é um cubo rsrsrs, e a arquitetura de rede neural, que seria o framework da LLM se a mesma fosse uma linguagem de programação), ela define a estrutura de tudo, calcula relações entre todos os tokens do prompt. Quanto mais tokens irrelevantes existem, mais o modelo precisa "dividir atenção" entre informações úteis e inúteis. &lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Sem escopo:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Me ensine Docker."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;O que acontece internamente: O modelo precisa decidir entre centenas de subtópicos, instalação, conceitos básicos, Dockerfile, docker-compose, volumes,  orquestração, a atenção se fragmenta e o resultado é um overview superficial de tudo.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Com escopo:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;"Explique o conceito de multi-stage build no Docker para um dev backend pleno que já usa
Docker no dia a dia mas nunca otimizou o tamanho das imagens. Mostre um exemplo prático
com uma aplicação JavaScript, comparando o Dockerfile sem e com multi-stage build,
incluindo o tamanho final de cada imagem."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;O que acontece internamente: O mecanismo de atenção se concentra em uma região muito específica, a interseção entre "Docker", "multi-stage build", "otimização de imagem" e "JavaScript". Os pesos de atenção ficam fortemente direcionados.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  3 - Forneça Contexto Relevante
&lt;/h3&gt;

&lt;p&gt;"Estado da aplicação"&lt;/p&gt;

&lt;p&gt;Uma LLM é &lt;em&gt;stateless&lt;/em&gt; por natureza, ela não tem memória entre requisições. Cada prompt é processado do zero, o único "estado" que ela tem é o que você coloca no prompt. Isso significa que todo contexto que você não fornece simplesmente não existe para o modelo.&lt;/p&gt;

&lt;p&gt;Internamente, o contexto funciona como um sistema de pesos no mecanismo de atenção. Quando você adiciona informações, elas criam "âncoras" que influenciam a distribuição de probabilidades de todos os tokens subsequentes, é como se cada pedaço de contexto fosse um ímã que puxa a resposta para uma direção específica.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Sem contexto:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Revise meu código."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;O que a LLM faz: Sem saber a linguagem, o framework, o nível do dev, o objetivo do código, o padrão do time ou o tipo de revisão esperada, ela vai fazer comentários genéricos: "adicione tratamento de erro", "use nomes mais descritivos", "considere adicionar testes".&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Com contexto:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Revise este endpoint Node.js com Express que lida com upload de arquivos para o S3.
O time usa ESLint + Prettier, então ignore estilo. O padrão do time é async/await com
try/catch e erros customizados. Endpoint em produção, recebe 200 uploads/min.
Foque em: memory leaks, tratamento de erros e uso correto do SDK do S3."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;O que a LLM faz: Cada informação do prompt funciona como um filtro que elimina ruído e concentra a revisão: "Node.js com Express + upload para S3" ativa conhecimento específico sobre streams, buffers, multipart e AWS SDK. "ESLint + Prettier, ignore estilo" elimina os comentários possíveis que o linter já resolve. "async/await com erros customizados" faz o modelo pular sugestões que o time já aplica e focar em como estão sendo usadas. "Produção, 200 uploads/min" muda o peso de cada problema, um buffer não liberado que seria aceitável em dev vira um incidente crítico sob carga. "Foque em: memory leaks, erros, SDK S3" restringe a revisão a 3 eixos e ignora dezenas de outros tópicos. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Com base nisso, saber as limitações do modelo que você está usando, te ajuda também a entender até aonde você pode ir nas perguntas e inferências, então escolha a sua melhor IA, treine ela, faça as suas perguntas, teste, tente! ;)&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;É Lembre-se: a LLM não "pensa", ela calcula probabilidades!&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Considerações Finais
&lt;/h1&gt;

&lt;p&gt;É isto, tentei resumir cada ponto que achei interessante abordar e explicar nessa primeira etapa, mas há muito mais sobre engenharia de prompt de LLM para falar, como técnicas mais clássicas de engenharia de prompts(Zero-shot prompting, Role prompting), técnicas mais avançadas(Chain-of-Thought (CoT), Prompt Chaining), são muitas camadas que tentarei destrinchar nos próximos artigos, esse é apenas o primeiro que traz a minha volta para a escrita de artigos após alguns bons anos. Espero que você caro leitor tenha entendido e aprendido algo. Obrigado por ler até aqui. ;)&lt;/p&gt;

&lt;p&gt;Onde me encontrar:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.linkedin.com/in/franciele-borges/" rel="noopener noreferrer"&gt;Meu LinkedIn&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/franSborges/" rel="noopener noreferrer"&gt;Meu GitHub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>beginners</category>
      <category>braziliandevs</category>
    </item>
    <item>
      <title>Do commit ao deploy: CI/CD de uma API na AWS usando GitHub Actions, ECS e Terraform</title>
      <dc:creator>Fernando Andrade</dc:creator>
      <pubDate>Thu, 12 Mar 2026 00:07:27 +0000</pubDate>
      <link>https://dev.to/he4rt/do-commit-ao-deploy-cicd-de-uma-api-na-aws-usando-github-actions-ecs-e-terraform-433g</link>
      <guid>https://dev.to/he4rt/do-commit-ao-deploy-cicd-de-uma-api-na-aws-usando-github-actions-ecs-e-terraform-433g</guid>
      <description>&lt;h2&gt;
  
  
  Sumário
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Introdução&lt;/li&gt;
&lt;li&gt;Pré-requisitos&lt;/li&gt;
&lt;li&gt;Visão Geral da Arquitetura&lt;/li&gt;
&lt;li&gt;Configurando o IAM para o Terraform&lt;/li&gt;
&lt;li&gt;
Infraestrutura como Código com Terraform

&lt;ul&gt;
&lt;li&gt;Recursos Provisionados&lt;/li&gt;
&lt;li&gt;IAM Role para Tarefas ECS&lt;/li&gt;
&lt;li&gt;Task Definition (Fargate)&lt;/li&gt;
&lt;li&gt;ECS Service&lt;/li&gt;
&lt;li&gt;OIDC: Autenticação Sem Credenciais Estáticas&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Dockerfile Multi-Stage&lt;/li&gt;

&lt;li&gt;Pipeline de CI&lt;/li&gt;

&lt;li&gt;Protegendo a Branch Main&lt;/li&gt;

&lt;li&gt;Configurando as Secrets no GitHub&lt;/li&gt;

&lt;li&gt;Pipeline de CD&lt;/li&gt;

&lt;li&gt;Acessando a Aplicação após o Deploy&lt;/li&gt;

&lt;li&gt;Segurança: OIDC em Detalhe&lt;/li&gt;

&lt;li&gt;Fluxo Completo: Do Commit ao Deploy&lt;/li&gt;

&lt;li&gt;Considerações Finais&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  Introdução
&lt;/h2&gt;

&lt;p&gt;Colocar uma aplicação em produção vai muito além de escrever código. Envolve compilar, testar, empacotar e entregar de forma confiável e repetível. Neste artigo, vou mostrar como construir uma pipeline completa — do commit ao deploy — usando &lt;strong&gt;GitHub Actions&lt;/strong&gt; para CI/CD, &lt;strong&gt;Terraform&lt;/strong&gt; para infraestrutura como código e &lt;strong&gt;AWS&lt;/strong&gt; (ECR, ECS Fargate) como plataforma de execução.&lt;/p&gt;

&lt;p&gt;O conceito apresentado aqui é &lt;strong&gt;agnóstico de linguagem&lt;/strong&gt; — funciona para qualquer stack que rode em um container Docker (Node.js, Go, Java, Python, etc.). Para os exemplos práticos, vamos utilizar &lt;strong&gt;.NET&lt;/strong&gt; como referência, mas os workflows, a infraestrutura Terraform e o fluxo de deploy são os mesmos independente da tecnologia escolhida.&lt;/p&gt;

&lt;p&gt;O objetivo é demonstrar como essas ferramentas se conectam para formar um fluxo automatizado onde um simples merge na branch &lt;code&gt;main&lt;/code&gt; resulta em uma nova versão rodando em produção, sem intervenção manual.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pré-requisitos
&lt;/h2&gt;

&lt;p&gt;Antes de começar, você precisa ter as seguintes ferramentas instaladas e configuradas:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Ferramenta&lt;/th&gt;
&lt;th&gt;Descrição&lt;/th&gt;
&lt;th&gt;Link de instalação&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Docker&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Para construir e executar containers&lt;/td&gt;
&lt;td&gt;&lt;a href="https://docs.docker.com/get-docker/" rel="noopener noreferrer"&gt;docs.docker.com/get-docker&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Terraform&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Para provisionar infraestrutura como código&lt;/td&gt;
&lt;td&gt;&lt;a href="https://developer.hashicorp.com/terraform/install" rel="noopener noreferrer"&gt;developer.hashicorp.com/terraform/install&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AWS CLI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Para interagir com os serviços da AWS via terminal&lt;/td&gt;
&lt;td&gt;&lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html" rel="noopener noreferrer"&gt;docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Git&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Para versionamento de código&lt;/td&gt;
&lt;td&gt;&lt;a href="https://git-scm.com/downloads" rel="noopener noreferrer"&gt;git-scm.com/downloads&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Conta AWS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Com permissões para criar recursos (IAM, ECS, ECR)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://aws.amazon.com/free/" rel="noopener noreferrer"&gt;aws.amazon.com/free&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Conta GitHub&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Para hospedar o repositório e rodar os workflows&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/" rel="noopener noreferrer"&gt;github.com&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nota:&lt;/strong&gt; Para o exemplo deste artigo, também é necessário o &lt;a href="https://dotnet.microsoft.com/download" rel="noopener noreferrer"&gt;.NET SDK&lt;/a&gt; instalado localmente para desenvolvimento. Se você estiver usando outra stack, substitua pelo SDK correspondente (Node.js, Go, JDK, etc.). Outro ponto é que a escolha em utilizar o ECS ao invés de um EKS ou EC2 é devido sua simplicidade na curva de aprendizado, baixo gerenciamento e que para fins de aprendizado os recursos mínimos definidos para esse laboratório não gerem altos gastos para o aprendizado.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Visão Geral da Arquitetura
&lt;/h2&gt;

&lt;p&gt;O fluxo completo funciona assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Developer → Feature Branch → Pull Request → Validação (CI)
                                                  ↓
                                            Merge na main
                                                  ↓
                                         Build &amp;amp; Push (CD)
                                                  ↓
                                        Deploy no ECS Fargate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Configurando o IAM para o Terraform
&lt;/h2&gt;

&lt;p&gt;Antes de rodar qualquer &lt;code&gt;terraform apply&lt;/code&gt;, é necessário que o Terraform tenha permissões para criar recursos na AWS. Para isso, precisamos de um &lt;strong&gt;usuário IAM&lt;/strong&gt; (ou role) com as permissões adequadas e configurar suas credenciais localmente.&lt;/p&gt;

&lt;h3&gt;
  
  
  Criando um usuário IAM para o Terraform
&lt;/h3&gt;

&lt;p&gt;No console da AWS (IAM &amp;gt; Users), crie um usuário dedicado para o Terraform:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Acesse &lt;strong&gt;IAM &amp;gt; Users &amp;gt; Create User&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Nomeie o usuário (ex: &lt;code&gt;terraform-deployer&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Anexe as policies necessárias para os recursos que serão criados:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AmazonECS_FullAccess
AmazonEC2ContainerRegistryFullAccess
AmazonVPCReadOnlyAccess
IAMFullAccess
CloudWatchLogsFullAccess
AmazonS3FullAccess
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nota de segurança:&lt;/strong&gt; Em um ambiente produtivo, o ideal é criar uma &lt;strong&gt;policy customizada&lt;/strong&gt; com o princípio do menor privilégio, concedendo apenas as permissões estritamente necessárias. Para fins de estudo, as managed policies acima simplificam o setup.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;Após criar o usuário, gere um &lt;strong&gt;Access Key&lt;/strong&gt; (IAM &amp;gt; Users &amp;gt; Security credentials &amp;gt; Create access key)&lt;/li&gt;
&lt;li&gt;Selecione o caso de uso &lt;strong&gt;Command Line Interface (CLI)&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Configurando as credenciais localmente
&lt;/h3&gt;

&lt;p&gt;Com o AWS CLI instalado, configure as credenciais:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws configure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Será solicitado:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWS Access Key ID: AKIA...
AWS Secret Access Key: wJal...
Default region name: us-east-1
Default output format: json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso cria o arquivo &lt;code&gt;~/.aws/credentials&lt;/code&gt; que o Terraform utilizará automaticamente via o provider AWS. Com isso feito, o Terraform tem autorização para provisionar os recursos que definiremos a seguir.&lt;/p&gt;




&lt;h2&gt;
  
  
  Infraestrutura como Código com Terraform
&lt;/h2&gt;

&lt;p&gt;Antes de qualquer pipeline rodar, a infraestrutura precisa existir. Com o Terraform, declaramos todos os recursos AWS em arquivos &lt;code&gt;.tf&lt;/code&gt; e provisionamos com um único comando.&lt;/p&gt;

&lt;h3&gt;
  
  
  Recursos Provisionados
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Provider AWS&lt;/span&gt;
&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Repositório ECR para armazenar imagens Docker&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_ecr_repository"&lt;/span&gt; &lt;span class="s2"&gt;"app_repository"&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;"minha-app-repository"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Cluster ECS&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_ecs_cluster"&lt;/span&gt; &lt;span class="s2"&gt;"app_cluster"&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;"minha-app-cluster"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  IAM Role para Tarefas ECS
&lt;/h3&gt;

&lt;p&gt;O ECS precisa de uma role para puxar imagens e enviar logs:&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="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_role"&lt;/span&gt; &lt;span class="s2"&gt;"ecs_task_execution_role"&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;"ecs-task-execution-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;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="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;"ecs-tasks.amazonaws.com"&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;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_role_policy_attachment"&lt;/span&gt; &lt;span class="s2"&gt;"ecs_policy"&lt;/span&gt; &lt;span class="p"&gt;{&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;ecs_task_execution_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;policy_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Task Definition (Fargate)
&lt;/h3&gt;

&lt;p&gt;Aqui definimos como o container será executado:&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="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_ecs_task_definition"&lt;/span&gt; &lt;span class="s2"&gt;"app_task"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;family&lt;/span&gt;                   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"minha-app-task"&lt;/span&gt;
  &lt;span class="nx"&gt;network_mode&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"awsvpc"&lt;/span&gt;
  &lt;span class="nx"&gt;requires_compatibilities&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"FARGATE"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;cpu&lt;/span&gt;                      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"256"&lt;/span&gt;
  &lt;span class="nx"&gt;memory&lt;/span&gt;                   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"512"&lt;/span&gt;
  &lt;span class="nx"&gt;execution_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;ecs_task_execution_role&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;container_definitions&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;name&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"app"&lt;/span&gt;
    &lt;span class="nx"&gt;image&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${aws_ecr_repository.app_repository.repository_url}:latest"&lt;/span&gt;
    &lt;span class="nx"&gt;portMappings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
      &lt;span class="nx"&gt;containerPort&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;
      &lt;span class="nx"&gt;hostPort&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;
    &lt;span class="p"&gt;}]&lt;/span&gt;
    &lt;span class="nx"&gt;logConfiguration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;logDriver&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"awslogs"&lt;/span&gt;
      &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"awslogs-group"&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/ecs/minha-app"&lt;/span&gt;
        &lt;span class="s2"&gt;"awslogs-region"&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;
        &lt;span class="s2"&gt;"awslogs-stream-prefix"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ecs"&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;h3&gt;
  
  
  ECS Service
&lt;/h3&gt;

&lt;p&gt;O service mantém o container rodando e gerencia o deploy:&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="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_ecs_service"&lt;/span&gt; &lt;span class="s2"&gt;"app_service"&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;"minha-app-service"&lt;/span&gt;
  &lt;span class="nx"&gt;cluster&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_ecs_cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app_cluster&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;task_definition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_ecs_task_definition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app_task&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;desired_count&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="nx"&gt;launch_type&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"FARGATE"&lt;/span&gt;

  &lt;span class="nx"&gt;network_configuration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;subnets&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_subnets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ids&lt;/span&gt;
    &lt;span class="nx"&gt;security_groups&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_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app_sg&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="nx"&gt;assign_public_ip&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&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;h3&gt;
  
  
  OIDC: Autenticação Sem Credenciais Estáticas
&lt;/h3&gt;

&lt;p&gt;Este é um dos pontos mais importantes da arquitetura. Em vez de armazenar &lt;code&gt;AWS_ACCESS_KEY&lt;/code&gt; e &lt;code&gt;AWS_SECRET_KEY&lt;/code&gt; como secrets no GitHub, usamos &lt;strong&gt;OIDC (OpenID Connect)&lt;/strong&gt; para que o GitHub Actions troque um token temporário por credenciais AWS.&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;# Registra o GitHub como provedor OIDC na AWS&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_openid_connect_provider"&lt;/span&gt; &lt;span class="s2"&gt;"github"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://token.actions.githubusercontent.com"&lt;/span&gt;
  &lt;span class="nx"&gt;client_id_list&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"sts.amazonaws.com"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;thumbprint_list&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ffffffffffffffffffffffffffffffffffffffff"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Role que o GitHub Actions vai assumir&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;"github_actions_role"&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;"github-actions-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;Federated&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_openid_connect_provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;github&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="nx"&gt;Action&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"sts:AssumeRoleWithWebIdentity"&lt;/span&gt;
      &lt;span class="nx"&gt;Condition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;StringEquals&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="s2"&gt;"token.actions.githubusercontent.com:aud"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"sts.amazonaws.com"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;StringLike&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="s2"&gt;"token.actions.githubusercontent.com:sub"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"repo:meu-usuario/meu-repo:ref:refs/heads/main"&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;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Por que isso importa?&lt;/strong&gt; Credenciais estáticas são um risco de segurança. Com OIDC, as credenciais são temporárias e o acesso é restrito a uma branch específica de um repositório específico. Mesmo que alguém tenha acesso ao repositório, não consegue assumir a role a partir de outra branch.&lt;/p&gt;




&lt;h2&gt;
  
  
  Dockerfile Multi-Stage
&lt;/h2&gt;

&lt;p&gt;Usamos um build multi-stage para separar o ambiente de compilação do ambiente de execução, resultando em uma imagem final menor e mais segura:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Stage 1: Build&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;mcr.microsoft.com/dotnet/sdk:10.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /src&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ["MeuProjeto/MeuProjeto.csproj", "MeuProjeto/"]&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet restore &lt;span class="s2"&gt;"MeuProjeto/MeuProjeto.csproj"&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; "/src/MeuProjeto"&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet build &lt;span class="nt"&gt;-c&lt;/span&gt; Release &lt;span class="nt"&gt;-o&lt;/span&gt; /app/build

&lt;span class="c"&gt;# Stage 2: Publish&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;publish&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet publish &lt;span class="nt"&gt;-c&lt;/span&gt; Release &lt;span class="nt"&gt;-o&lt;/span&gt; /app/publish /p:UseAppHost&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;

&lt;span class="c"&gt;# Stage 3: Runtime&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;mcr.microsoft.com/dotnet/aspnet:10.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;final&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8080&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=publish /app/publish .&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["dotnet", "MeuProjeto.dll"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Benefícios do multi-stage:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A imagem final contém apenas o runtime, não o SDK completo&lt;/li&gt;
&lt;li&gt;Reduz significativamente o tamanho da imagem&lt;/li&gt;
&lt;li&gt;O código-fonte não fica presente na imagem de produção&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Pipeline de CI
&lt;/h2&gt;

&lt;p&gt;A primeira pipeline roda automaticamente quando um Pull Request é aberto contra a branch &lt;code&gt;main&lt;/code&gt;. Seu objetivo é validar que o código compila e que os testes passam.&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PR Validation&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&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;Setup .NET&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-dotnet@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;dotnet-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;10.0.x'&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;Restore&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dotnet restore MinhaSolution.sln&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;Build&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dotnet build MinhaSolution.sln --no-restore -c Release&lt;/span&gt;

  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&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;Setup .NET&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-dotnet@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;dotnet-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;10.0.x'&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;Run Tests with Coverage&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;dotnet test MinhaSolution.sln \&lt;/span&gt;
            &lt;span class="s"&gt;--collect:"XPlat Code Coverage" \&lt;/span&gt;
            &lt;span class="s"&gt;--results-directory ./coverage&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  O que acontece nesta pipeline:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Job &lt;code&gt;build&lt;/code&gt;&lt;/strong&gt; — Compila a solução para garantir que não há erros de compilação&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Job &lt;code&gt;test&lt;/code&gt;&lt;/strong&gt; — Roda os testes unitários com cobertura de código usando Coverlet&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Separar em dois jobs traz clareza: se o build falha, você sabe que é erro de compilação. Se o test falha, o código compila mas tem um bug.&lt;/p&gt;




&lt;h2&gt;
  
  
  Protegendo a Branch Main
&lt;/h2&gt;

&lt;p&gt;Com a pipeline de CI configurada, é fundamental garantir que &lt;strong&gt;nenhum código chegue à &lt;code&gt;main&lt;/code&gt; sem passar pela validação&lt;/strong&gt;. Para isso, configuramos uma &lt;strong&gt;branch protection rule&lt;/strong&gt; no GitHub.&lt;/p&gt;

&lt;p&gt;Acesse &lt;strong&gt;Settings &amp;gt; Branches &amp;gt; Add branch protection rule&lt;/strong&gt; no seu repositório e configure:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Branch name pattern:&lt;/strong&gt; &lt;code&gt;main&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Marque &lt;strong&gt;Require a pull request before merging&lt;/strong&gt; — impede push direto na main&lt;/li&gt;
&lt;li&gt;Marque &lt;strong&gt;Require status checks to pass before merging&lt;/strong&gt; — bloqueia o merge até que os checks passem&lt;/li&gt;
&lt;li&gt;Em &lt;strong&gt;Status checks that are required&lt;/strong&gt;, busque e adicione os jobs da pipeline de CI:

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;build&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;test&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nota:&lt;/strong&gt; Na primeira vez que configurar, os status checks podem não aparecer na busca. Eles só ficam disponíveis após a pipeline rodar pelo menos uma vez no repositório. Abra um PR de teste para que os checks sejam registrados.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Configurando as Secrets no GitHub
&lt;/h2&gt;

&lt;p&gt;Antes de configurar a pipeline de deploy, é necessário cadastrar as &lt;strong&gt;secrets&lt;/strong&gt; no repositório do GitHub. A pipeline de CD depende delas para autenticar na AWS e saber para onde enviar a imagem Docker.&lt;/p&gt;

&lt;p&gt;Acesse &lt;strong&gt;Settings &amp;gt; Secrets and variables &amp;gt; Actions &amp;gt; New repository secret&lt;/strong&gt; no seu repositório e crie as seguintes secrets:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Secret&lt;/th&gt;
&lt;th&gt;Valor&lt;/th&gt;
&lt;th&gt;Exemplo&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AWS_ROLE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;O &lt;strong&gt;ARN completo&lt;/strong&gt; da IAM Role criada para o GitHub Actions&lt;/td&gt;
&lt;td&gt;&lt;code&gt;arn:aws:iam::123456789012:role/github-actions-role&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ECR_REPOSITORY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A &lt;strong&gt;URI completa&lt;/strong&gt; do repositório ECR (não apenas o nome)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;123456789012.dkr.ecr.us-east-1.amazonaws.com/minha-app-repository&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AWS_ACCOUNT_ID&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;O ID numérico da sua conta AWS&lt;/td&gt;
&lt;td&gt;&lt;code&gt;123456789012&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Atenção:&lt;/strong&gt; Um erro comum é colocar apenas o nome do repositório ECR (ex: &lt;code&gt;minha-app-repository&lt;/code&gt;) na secret &lt;code&gt;ECR_REPOSITORY&lt;/code&gt;. O valor correto é a &lt;strong&gt;URI completa&lt;/strong&gt; que inclui o domínio do ECR. Você pode obter essa URI no console da AWS em &lt;strong&gt;ECR &amp;gt; Repositories&lt;/strong&gt; ou via CLI:&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws ecr describe-repositories &lt;span class="nt"&gt;--repository-names&lt;/span&gt; minha-app-repository &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s2"&gt;"repositories[0].repositoryUri"&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; text
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Da mesma forma, a secret &lt;code&gt;AWS_ROLE&lt;/code&gt; deve conter o &lt;strong&gt;ARN&lt;/strong&gt; (Amazon Resource Name) completo da role, não apenas o nome. Para consultar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws iam get-role &lt;span class="nt"&gt;--role-name&lt;/span&gt; github-actions-role &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s2"&gt;"Role.Arn"&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; text
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Com as secrets configuradas, a pipeline de deploy consegue autenticar via OIDC, fazer push da imagem para o ECR e disparar o deploy no ECS.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pipeline de CD
&lt;/h2&gt;

&lt;p&gt;Quando o PR é aprovado e mergeado na &lt;code&gt;main&lt;/code&gt;, a pipeline de deploy entra em açã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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build and Deploy&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#permissões necessárias para autenticação OIDC&lt;/span&gt;
  &lt;span class="na"&gt;id-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&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;AWS_REGION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;us-east-1&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&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;Configure AWS Credentials (OIDC)&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/configure-aws-credentials@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;role-to-assume&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_ROLE_ARN }}&lt;/span&gt;
          &lt;span class="na"&gt;aws-region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.AWS_REGION }}&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;Login to Amazon ECR&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/amazon-ecr-login@v2&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;Build Docker Image&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker build -t minha-app .&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;Tag Image&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker tag minha-app:latest ${{ secrets.ECR_REPOSITORY }}:latest&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;Push to ECR&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker push ${{ secrets.ECR_REPOSITORY }}:latest&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;Deploy to ECS&lt;/span&gt;
        &lt;span class="na"&gt;run&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 ecs update-service \&lt;/span&gt;
            &lt;span class="s"&gt;--cluster minha-app-cluster \&lt;/span&gt;
            &lt;span class="s"&gt;--service minha-app-service \&lt;/span&gt;
            &lt;span class="s"&gt;--force-new-deployment&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  O que acontece nesta pipeline:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Autenticação OIDC&lt;/strong&gt; — O GitHub troca seu token JWT por credenciais AWS temporárias&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Login no ECR&lt;/strong&gt; — Autentica o Docker no registro da AWS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build e Push&lt;/strong&gt; — Constrói a imagem Docker e envia para o ECR&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deploy&lt;/strong&gt; — Dispara um novo deployment no ECS, que puxa a imagem atualizada e substitui o container antigo&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;O &lt;code&gt;--force-new-deployment&lt;/code&gt; garante que o ECS vai iniciar uma nova task mesmo que a tag da imagem (&lt;code&gt;latest&lt;/code&gt;) não tenha mudado.&lt;/p&gt;




&lt;h2&gt;
  
  
  Acessando a Aplicação após o Deploy
&lt;/h2&gt;

&lt;p&gt;Após a pipeline de CD concluir com sucesso, a aplicação estará rodando no ECS Fargate. Como configuramos &lt;code&gt;assign_public_ip = true&lt;/code&gt; no Terraform, a task recebe um &lt;strong&gt;IP público&lt;/strong&gt; que pode ser usado para acessar a API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Obtendo o IP público pelo Console AWS
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Acesse &lt;strong&gt;ECS &amp;gt; Clusters &amp;gt; minha-app-cluster&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Clique na aba &lt;strong&gt;Tasks&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Clique na task em execução (status &lt;code&gt;RUNNING&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Na seção &lt;strong&gt;Network&lt;/strong&gt;, copie o &lt;strong&gt;Public IP&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Acesse no navegador: &lt;code&gt;http://&amp;lt;PUBLIC_IP&amp;gt;:8080&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Obtendo o IP público via CLI
&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;# 1. Obtenha o ARN da task em execução&lt;/span&gt;
&lt;span class="nv"&gt;TASK_ARN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;aws ecs list-tasks &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cluster&lt;/span&gt; minha-app-cluster &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--service-name&lt;/span&gt; minha-app-service &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s2"&gt;"taskArns[0]"&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; text&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# 2. Obtenha o ID da interface de rede (ENI)&lt;/span&gt;
&lt;span class="nv"&gt;ENI_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;aws ecs describe-tasks &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cluster&lt;/span&gt; minha-app-cluster &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tasks&lt;/span&gt; &lt;span class="nv"&gt;$TASK_ARN&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s2"&gt;"tasks[0].attachments[0].details[?name=='networkInterfaceId'].value"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; text&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# 3. Obtenha o IP público&lt;/span&gt;
aws ec2 describe-network-interfaces &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--network-interface-ids&lt;/span&gt; &lt;span class="nv"&gt;$ENI_ID&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s2"&gt;"NetworkInterfaces[0].Association.PublicIp"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; text
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Importante:&lt;/strong&gt; O IP público muda toda vez que uma nova task é criada (ou seja, a cada deploy). Para um ambiente produtivo, o ideal é utilizar um &lt;strong&gt;Application Load Balancer (ALB)&lt;/strong&gt; ou um &lt;strong&gt;domínio com Route 53&lt;/strong&gt; apontando para o serviço ECS, garantindo um endereço fixo. Para fins de estudo, o IP público direto é suficiente.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Segurança: OIDC em Detalhe
&lt;/h2&gt;

&lt;p&gt;Vale reforçar a importância do OIDC neste fluxo. O modelo tradicional funciona assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❌ Modelo Tradicional:
   GitHub Secrets → AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY
   - Credenciais estáticas que nunca expiram
   - Se vazadas, acesso total até serem rotacionadas manualmente
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Com OIDC:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✅ Modelo OIDC:
   GitHub Actions → JWT Token → AWS STS → Credenciais Temporárias
   - Credenciais expiram automaticamente
   - Escopo restrito: apenas uma branch de um repositório específico
   - Sem segredos de longa duração armazenados
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A configuração requer:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Registrar o GitHub como OIDC Provider na AWS (via Terraform)&lt;/li&gt;
&lt;li&gt;Criar uma IAM Role com trust policy apontando para o repositório&lt;/li&gt;
&lt;li&gt;No workflow, usar &lt;code&gt;permissions: id-token: write&lt;/code&gt; e a action &lt;code&gt;configure-aws-credentials&lt;/code&gt; com &lt;code&gt;role-to-assume&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Fluxo Completo: Do Commit ao Deploy
&lt;/h2&gt;

&lt;p&gt;Resumindo o ciclo de vida de uma mudança:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;1. git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; feature/minha-feature
2. &lt;span class="c"&gt;# Desenvolve e commita&lt;/span&gt;
3. git push origin feature/minha-feature
4. &lt;span class="c"&gt;# Abre Pull Request → Dispara pr-validation.yml&lt;/span&gt;
   │
   ├── ✅ Build compila com sucesso
   └── ✅ Testes passam com cobertura
   │
5. &lt;span class="c"&gt;# Code review + Aprovação&lt;/span&gt;
6. &lt;span class="c"&gt;# Merge na main → Dispara build-and-deploy.yml&lt;/span&gt;
   │
   ├── 🔐 Autenticação via OIDC
   ├── 🐳 Build da imagem Docker &lt;span class="o"&gt;(&lt;/span&gt;multi-stage&lt;span class="o"&gt;)&lt;/span&gt;
   ├── 📦 Push para o ECR
   └── 🚀 Deploy no ECS Fargate
   │
7. &lt;span class="c"&gt;# Nova versão rodando em produção&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Considerações Finais
&lt;/h2&gt;

&lt;p&gt;Este setup demonstra como é possível construir uma pipeline profissional de CI/CD com ferramentas modernas e boas práticas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Infraestrutura como Código&lt;/strong&gt; — Toda a infraestrutura é versionada e reproduzível&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Autenticação Keyless&lt;/strong&gt; — OIDC elimina o risco de credenciais estáticas&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Serverless Containers&lt;/strong&gt; — Fargate remove a necessidade de gerenciar servidores&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separação CI/CD&lt;/strong&gt; — Validação em PRs e deploy apenas na main&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Imagens otimizadas&lt;/strong&gt; — Multi-stage build reduz a superfície de ataque&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;O custo de infraestrutura para um setup como esse é mínimo — com Fargate usando 0.25 vCPU e 512MB de memória, o custo fica na faixa de poucos dólares por mês para ambientes de estudo e projetos pequenos.&lt;/p&gt;

&lt;p&gt;A barreira de entrada para CI/CD profissional diminuiu muito. Ferramentas como GitHub Actions e Terraform tornam acessível o que antes exigia equipes dedicadas de DevOps. O importante é começar simples, entender cada peça, e evoluir conforme a necessidade.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Este artigo foi escrito com base em um projeto prático de estudo. Todo o código-fonte está disponível publicamente neste repositório do &lt;a href="https://github.com/fernanduandrade/api-quality-lab" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>devops</category>
      <category>aws</category>
      <category>braziliandevs</category>
    </item>
    <item>
      <title>Criei uma pipeline de planejamento no OpenCode e olha no que deu</title>
      <dc:creator>Clinton Rocha</dc:creator>
      <pubDate>Mon, 09 Mar 2026 04:37:00 +0000</pubDate>
      <link>https://dev.to/he4rt/sistema-de-planejamento-estruturado-no-opencode-16pb</link>
      <guid>https://dev.to/he4rt/sistema-de-planejamento-estruturado-no-opencode-16pb</guid>
      <description>&lt;p&gt;Você é como eu e vem buscando formas de otimizar o uso de ferramentas como o &lt;a href="https://opencode.ai/docs/pt-br" rel="noopener noreferrer"&gt;OpenCode&lt;/a&gt;? Talvez este conteúdo te ajude a ter novas ideias e até mesmo entender alguns fluxos dessa e de outras ferramentas.&lt;/p&gt;

&lt;p&gt;Enquanto eu estudava a documentação do OpenCode, encontrei a aba de &lt;a href="https://pt.wikipedia.org/wiki/Plug-in" rel="noopener noreferrer"&gt;plugins&lt;/a&gt; feitos pela comunidade. Ali, encontrei três plugins que, na minha visão, se complementam bem. Na verdade, um deles eu já utilizava: o &lt;a href="https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin" rel="noopener noreferrer"&gt;&lt;strong&gt;Plannotator&lt;/strong&gt;&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;A partir disso, comecei a estudar como funcionava os comandos, agentes e como eu poderia fazer um fluxo que fizesse sentido e principalmente que funcionasse, calma que já te explico oq cada um dos plugins fazem e qual a ideia final da integração.&lt;/p&gt;

&lt;h1&gt;
  
  
  Índice
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Como eu estava usando o OpenCode&lt;/li&gt;
&lt;li&gt;Objetivo da integração&lt;/li&gt;
&lt;li&gt;Plannotator: Revisão Visual&lt;/li&gt;
&lt;li&gt;Octto: Coleta estruturada de contexto&lt;/li&gt;
&lt;li&gt;Subtask2: Orquestração de Pipeline&lt;/li&gt;
&lt;li&gt;Comandos&lt;/li&gt;
&lt;li&gt;Considerações&lt;/li&gt;
&lt;li&gt;Referências&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Como eu estava usando o OpenCode
&lt;/h1&gt;

&lt;p&gt;Após começar a utilizar o OpenCode, notei o quão importante é o uso do módulo de planejamento e, principalmente, não apenas o planejamento em si, mas também as perguntas que são feitas durante esse processo. O resultado dessas duas novas abordagens (planejamento e perguntas) melhora bastante não só o código produzido, mas também a minha compreensão sobre a regra de negócio, além de comportamentos e fluxos do sistema.&lt;/p&gt;

&lt;p&gt;Mesmo com todas essas descobertas que trouxeram otimizações para o processo, eu ainda sentia que dava para melhorar mais.&lt;/p&gt;

&lt;p&gt;O fluxo de uso do OpenCode era basicamente o seguinte: eu enviava o prompt, a ferramenta vasculhava o código para obter contexto, depois vinham algumas perguntas e, em seguida, ela ia para a parte de &lt;em&gt;build&lt;/em&gt;, ou seja, para colocar em prática o que tinha sido planejado.&lt;/p&gt;

&lt;h1&gt;
  
  
  Objetivo da integração
&lt;/h1&gt;

&lt;p&gt;A ideia vai ser conseguir extrair o máximo de contexto com o &lt;code&gt;Octto&lt;/code&gt;, usar o &lt;code&gt;subtask2&lt;/code&gt; para orquestrar os comandos e visualizar o plano final com &lt;code&gt;Plannotator&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+---------------------------------------------------------------+
|                                                               |
|  IDEIA -&amp;gt; CONTEXTO -&amp;gt; PLANO -&amp;gt; REFINAMENTO -&amp;gt; REVISAO -&amp;gt; OK   |
|                                                               |
+---------------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O resultado é um &lt;strong&gt;único comando&lt;/strong&gt; que orquestra todo o processo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/generate-plan &lt;span class="s2"&gt;"descrição do objetivo"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Plannotator: Revisão visual
&lt;/h1&gt;

&lt;p&gt;O primeiro plugin chegou até mim por recomendação de uma amiga da comunidade (&lt;a href="https://github.com/he4rt/" rel="noopener noreferrer"&gt;He4rtDevs&lt;/a&gt;), a &lt;a href="https://dev.to/cherryramatis"&gt;Cherry&lt;/a&gt;, e o plugin era o &lt;a href="https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin" rel="noopener noreferrer"&gt;Plannotator&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Esse plugin basicamente faz com que, ao final de um planejamento, seja aberta uma página no navegador contendo todo o plano gerado. Isso facilita muito a visualização em comparação com o terminal. Na interface do navegador, você consegue selecionar trechos do planejamento e dar feedback, que retorna para o processo de planejamento. Também é possível aprovar o plano e seguir diretamente para a etapa de &lt;em&gt;build&lt;/em&gt;, ou seja, aplicar o que foi planejado.&lt;/p&gt;

&lt;h1&gt;
  
  
  Octto: Coleta estruturada de contexto
&lt;/h1&gt;

&lt;p&gt;O &lt;a href="https://github.com/vtemian/octto" rel="noopener noreferrer"&gt;Octto&lt;/a&gt; quando você envia o primeiro prompt, ele abre uma página no navegador com perguntas relacionadas ao que você pediu inicialmente. Essa página funciona como uma interface interativa de brainstorming. Conforme você responde às perguntas, o sistema utiliza essas respostas para gerar novas perguntas dentro da mesma sessão, aprofundando o entendimento do problema e ajudando a estruturar melhor o contexto antes de seguir para as próximas etapas, ao final ele gera um arquivo &lt;code&gt;.md&lt;/code&gt; no diretório &lt;code&gt;/docs/plans&lt;/code&gt; dentro do projeto.&lt;/p&gt;

&lt;p&gt;A unica configuração que adicionei a ele, foi uma que faz com que a primeira pergunta seja um campo de texto livre onde o usuário consiga descrever o contexto com maior liberdade (colocar uma task completa, por exemplo).&lt;/p&gt;

&lt;h2&gt;
  
  
  Fluxo de Funcionamento
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+----------------------+
|       ask_text       |  &amp;lt;-- Primeira ação obrigatória
+----------+-----------+
           |
           | Campo de texto no browser:
           | "Descreva com detalhes o que
           | você quer planejar..."
           v
+----------------------+
|   create_brainstorm  |  &amp;lt;-- Agente inicia sessão
+----------+-----------+      com contexto coletado
           |
           | Define branches de exploração
           v
+--------------------------------------------------------------+
|                    BRANCHES DE EXPLORAÇÃO                    |
|                                                              |
|  +----------------+  +-------------------+  +---------------+|
|  | Motivação &amp;amp;    |  | Requisitos &amp;amp;      |  | Riscos &amp;amp;      ||
|  | Objetivos      |  | Restrições        |  | Dependências  ||
|  +--------+-------+  +---------+---------+  +-------+-------+|
|           |                    |                    |        |
+-----------+--------------------+--------------------+--------+
            |
            v
+----------------------+
|      BROWSER UI      |
|                      |
|  [ Pergunta 1 ]      |
|  [ Pergunta 2 ]      |
|  [ Pergunta N ]      |
|                      |
|      [Responder]     |
+----------+-----------+
           |
           | Usuário responde
           v
+----------------------+
| await_brainstorm_    |
| complete             |  &amp;lt;-- Agente aguarda respostas
+----------+-----------+
           |
           | Sintetiza respostas
           v
+----------------------+
|      docs/plans/     |
| 2026-03-08-objetivo  |  &amp;lt;-- Arquivo gerado
|        .md           |
+----------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configuração via Fragments
&lt;/h2&gt;

&lt;p&gt;O comportamento do Octto é customizado através de &lt;strong&gt;fragments&lt;/strong&gt;, instruções adicionadas ao prompt dos agentes internos:&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;"fragments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"octto"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"Sempre pergunte a motivação e o 'porquê' das mudanças"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"Gere nomes de arquivo no formato: YYYY-MM-DD-slug.md"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"Salve os planos em docs/plans/"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"probe"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"Priorize perguntas sobre motivações e restrições"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"Inclua perguntas sobre requisitos não-funcionais"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"bootstrapper"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"Crie branches focados em: requisitos, arquitetura, riscos"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Função de cada agente interno:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;bootstrapper&lt;/strong&gt;: cria a estrutura inicial de branches&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;probe&lt;/strong&gt; : gera perguntas de aprofundamento&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;octto&lt;/strong&gt;: define o comportamento geral da coleta de contexto&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Subtask2: Orquestração de pipeline
&lt;/h1&gt;

&lt;p&gt;O &lt;a href="https://github.com/spoons-and-mirrors/subtask2" rel="noopener noreferrer"&gt;Subtask2&lt;/a&gt; permite &lt;strong&gt;encadear comandos&lt;/strong&gt; de forma sequencial. Quando um comando termina, o próximo da cadeia é executado automaticamente. &lt;/p&gt;

&lt;p&gt;Nesse meu contexto só vou usar a funcionalidade de &lt;code&gt;return&lt;/code&gt;, mas esse plugin tem muitas outras funcionalidades interessantes como:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;loop&lt;/code&gt; de subtarefas até que a condição do usuário seja atendida&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;paralela&lt;/code&gt; de subtarefas simultaneamente - PR pendente.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;$TURN[n]&lt;/code&gt; passa turnos da sessão (mensagens do usuário/assistente).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;{as:name}&lt;/code&gt; + &lt;code&gt;$RESULT[name]&lt;/code&gt; captura e referência as saídas das subtarefas.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Sintaxe de Return Chain
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;subtask&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;return&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/comando-1&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/comando-2&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/comando-3&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Comandos
&lt;/h1&gt;

&lt;p&gt;Com o &lt;code&gt;subtask2&lt;/code&gt;, consigo adicionar comandos ao fluxo, e isso abre muitas possibilidades. Mas, por enquanto, estou com os pés no chão e vou fazer um fluxo simples com alguns novos comandos.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Eu criei esses comandos com o único intuito de usar na pipeline.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Comando Principal
&lt;/h2&gt;

&lt;p&gt;O comando que vai dar inicio a todo o fluxo, vai ser o &lt;code&gt;/generate-plan&lt;/code&gt; ao executar, ele da inicio a pipeline de planejamento.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Você é livre para escolher qualquer nome para o comando, para escolher o nome desse aqui eu só segui as vozes da minha cabeça.&lt;/p&gt;

&lt;p&gt;Caso queira saber mais sobre como criar e configurar &lt;a href="https://opencode.ai/docs/pt-br/commands/" rel="noopener noreferrer"&gt;comandos no opencode&lt;/a&gt;, a doc é legalzinha.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/generate-plan refatorar sistema de autenticação
/generate-plan &lt;span class="s2"&gt;"adicionar suporte a múltiplos gateways de pagamento"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As aspas são opcionais: tudo após o comando é passado como &lt;code&gt;$ARGUMENTS&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pipeline completo de planejamento&lt;/span&gt; 
&lt;span class="na"&gt;agent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;octto&lt;/span&gt;
&lt;span class="na"&gt;subtask&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;return&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/refine-plan&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/review-plan&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

prompt aqui, e lembre de usar o $ARGUMENTS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Caso você queira ver como deixei o meu &lt;a href="https://gist.github.com/Clintonrocha98/f9f5cb146a799a3a2a340ef12a0bf9bf#file-prompt-generate-plan-md" rel="noopener noreferrer"&gt;prompt usado no comando generate-plan&lt;/a&gt; é só ir no gist.&lt;/p&gt;

&lt;p&gt;Todos os comandos podem ser usados individualmente/isolado no OpenCode, a unica diferença ao usar um comando com o &lt;code&gt;subtask&lt;/code&gt; é que ele vai ser executado em um contexto diferente.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Refinamento Técnico
&lt;/h2&gt;

&lt;p&gt;O &lt;code&gt;/refine-plan&lt;/code&gt; tem como objetivo expandir um plano existente com detalhes arquiteturais.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/refine-plan &lt;span class="c"&gt;# usa plano mais recente&lt;/span&gt;
/refine-plan docs/plans/meu-plano.md &lt;span class="c"&gt;# plano específico&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;refine-plan&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Expande e refina tecnicamente um plano existente com arquitetura e diagramas&lt;/span&gt;
&lt;span class="na"&gt;subtask&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

Mesma coisa que o anterior, aqui você consegue adicionar a instrução que você quiser.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;No meu caso, o prompt que estou sando tá no &lt;a href="https://gist.github.com/Clintonrocha98/f9f5cb146a799a3a2a340ef12a0bf9bf#file-refine-plan-md" rel="noopener noreferrer"&gt;gist 'refine-plan.md'&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Revisão Visual
&lt;/h2&gt;

&lt;p&gt;Por último vem o comando &lt;code&gt;/review-plan&lt;/code&gt; com o objetivo de mandar o plano para revisão interativa via Plannotator.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/review-plan &lt;span class="c"&gt;# usa plano mais recente&lt;/span&gt;
/review-plan docs/plans/meu-plano.md &lt;span class="c"&gt;# plano específico&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Importante:&lt;/strong&gt; Este comando &lt;strong&gt;não usa &lt;code&gt;subtask: true&lt;/code&gt;&lt;/strong&gt;. Ele roda na sessão primária para que a ferramenta &lt;code&gt;submit_plan&lt;/code&gt; possa abrir a interface do Plannotator no browser corretamente. Comandos rodando como subtarefa não conseguem inicializar a UI do browser.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fluxo Completo de Execução:&lt;/strong&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%2Fc22oiuapqumis0k3m5ns.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%2Fc22oiuapqumis0k3m5ns.png" alt="diagrama contendo todo o fluxo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Considerações
&lt;/h1&gt;

&lt;p&gt;O mais importante de tudo: funcionou? Sim. É uma bazuca? Sim, hahahaha. Esse projeto acabou saindo porque entrei em hiperfoco ao me fazer a pergunta “será que funciona?”. No fim, tive minha resposta.&lt;/p&gt;

&lt;p&gt;Olhando para a usabilidade no dia a dia, acredito que os melhores momentos para utilizar algo assim são em novas &lt;em&gt;features&lt;/em&gt;, refatoração de projeto e até mesmo para estruturar estudos, quem sabe. Só não use para algo como “quero trocar a cor de um botão”, aí você acaba complicando.&lt;/p&gt;

&lt;p&gt;O ponto que mais curti foi o &lt;code&gt;Octto&lt;/code&gt;, com as perguntas geradas de forma dinâmica. Aquilo ali, ao meu ver, auxilia na contextualização de uma baita maneira. Fora que é possível dar instruções para as &lt;em&gt;branches&lt;/em&gt; dele, especificar qual tipo de input você quer ou não quer, entre outras coisas (vale ler a documentação).&lt;/p&gt;

&lt;p&gt;Já o &lt;code&gt;subtask2&lt;/code&gt; abre portas para muita coisa. Como eu tinha dito antes, acabei indo pelo simples, mas alguém com a mente mais aberta provavelmente vai conseguir extrair o melhor da ferramenta.&lt;/p&gt;

&lt;p&gt;Eu pretendo criar novos comandos, refinar mais os &lt;em&gt;prompts&lt;/em&gt; e ver o que acontece. É isso, obrigado por ler até aqui e tente brincar com isso ai.&lt;/p&gt;

&lt;p&gt;Como meu &lt;em&gt;Tech Lead&lt;/em&gt; fala: &lt;/p&gt;

&lt;p&gt;&lt;code&gt;Faça codigo inútil e quebre alguma coisa.&lt;/code&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Referências
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://opencode.ai/docs/commands/" rel="noopener noreferrer"&gt;OpenCode - Comandos&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://opencode.ai/docs/agents/" rel="noopener noreferrer"&gt;OpenCode - Agentes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/spoons-and-mirrors/subtask2" rel="noopener noreferrer"&gt;Subtask2 - GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin" rel="noopener noreferrer"&gt;Plannotator - Plugin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/vtemian/octto" rel="noopener noreferrer"&gt;Octto - GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/%40summer12126/what-does-adhoc-mean-in-sw-development-2196f09826d6" rel="noopener noreferrer"&gt;what does ad-hoc&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>braziliandevs</category>
      <category>opensource</category>
    </item>
    <item>
      <title>API vs Event Streaming: O Que Muda Para Quem Testa?</title>
      <dc:creator>Alicia Marianne 🇧🇷 </dc:creator>
      <pubDate>Thu, 12 Feb 2026 20:50:04 +0000</pubDate>
      <link>https://dev.to/he4rt/api-vs-event-streaming-o-que-muda-para-quem-testa-1f1h</link>
      <guid>https://dev.to/he4rt/api-vs-event-streaming-o-que-muda-para-quem-testa-1f1h</guid>
      <description>&lt;p&gt;✨ &lt;strong&gt;Oi, gente!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Recentemente, tenho pensado bastante em como contribuir para a comunidade de QA e ajudar aqueles que estão começando ou que já estão há algum tempo na área. Também quero relatar mais sobre os desafios de ser uma brasileira morando em terras lusitanas 🇵🇹.&lt;/p&gt;

&lt;p&gt;O que me levou a querer voltar — na verdade, começar — a escrever na minha língua materna?&lt;/p&gt;

&lt;p&gt;Bom, se eu fosse dizer de bate-pronto: saudade. 💛&lt;/p&gt;

&lt;p&gt;Sim, aqui em Portugal fala-se português também, porém, como boa mineira que sou, sinto falta do nosso jeitinho de falar e escrever as coisas. Acredito que falar sobre o que sei na minha língua materna me deixa mais próxima de casa.&lt;/p&gt;

&lt;p&gt;Outra razão que me fez querer começar a escrever em português foi voltar a ser mais ativa em comunidades de desenvolvimento (&lt;a href="https://discord.gg/he4rt" rel="noopener noreferrer"&gt;He4rt&lt;/a&gt; 💜). Tenho visto muitas mulheres entrando para a área, e algumas não são fluentes em inglês ou não confiam no que sabem para se arriscar em conteúdos em outros idiomas.&lt;/p&gt;

&lt;p&gt;Com isso, eu pensei: por que não focar por um tempo em escrever em português? E cá estamos! ✍️&lt;/p&gt;

&lt;p&gt;E, para que isso não seja apenas um comunicado, mas algo útil, já vou começar falando sobre serviços de mensageria (Event Streaming) e por que é importante que um QA (ou aspirante a QA) entenda pelo menos o básico sobre isso.&lt;/p&gt;




&lt;h2&gt;
  
  
  📡 Em termos técnicos
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Event Streaming (Transmissão de Eventos)&lt;/strong&gt; é a prática de capturar dados em tempo real a partir de fontes como bancos de dados, sensores, dispositivos móveis, serviços na nuvem e aplicações de software, na forma de fluxos de eventos; armazenar esses fluxos de forma duradoura para recuperação posterior; manipular, processar e reagir a esses eventos tanto em tempo real quanto retrospectivamente; e encaminhá-los para diferentes destinos, conforme necessário.&lt;/p&gt;

&lt;p&gt;Lindo, né? 😅&lt;/p&gt;

&lt;p&gt;Mas como podemos entender isso na prática?&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 Vamos imaginar o seu corpo
&lt;/h2&gt;

&lt;p&gt;Sim, ele mesmo!&lt;/p&gt;

&lt;p&gt;Você sente aquela dor no estômago 🍽️, que é um sinal de que está com fome. Esse sinal “fala” para o seu cérebro:&lt;/p&gt;

&lt;p&gt;— Querido, estou precisando de nutrientes. Quando eles chegam?&lt;/p&gt;

&lt;p&gt;Normalmente, você para o que está fazendo e vai comer algo. Mas perceba: seu estômago precisa esperar até o momento certo. Ainda assim, seu corpo continua funcionando normalmente. Ele não para.&lt;/p&gt;

&lt;p&gt;No mundo do software, isso significa que, sempre que existir um sinal ou uma modificação, essa informação será interpretada &lt;strong&gt;sem que outras funcionalidades do sistema sejam interrompidas&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🛒 Exemplo prático: compras online
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Você faz o seu pedido&lt;/li&gt;
&lt;li&gt;Realiza o pagamento&lt;/li&gt;
&lt;li&gt;O sistema de pagamento processa esse pagamento&lt;/li&gt;
&lt;li&gt;Ele publica um evento informando que o pagamento foi aprovado&lt;/li&gt;
&lt;li&gt;O sistema de notificação consome esse evento&lt;/li&gt;
&lt;li&gt;E o cliente recebe a confirmação de que a compra será entregue 📦&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tudo isso pode acontecer sem que um sistema precise “esperar parado” pelo outro.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔧 Algumas ferramentas de Event Streaming
&lt;/h2&gt;

&lt;p&gt;Hoje, existem várias ferramentas no mercado que trabalham com esse modelo de comunicação baseada em eventos. Algumas das mais conhecidas são:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Apache Kafka&lt;/strong&gt; 🐘 (uma das mais populares no mercado)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RabbitMQ&lt;/strong&gt; 🐰&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AWS Kinesis&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cada uma tem suas particularidades, mas todas trabalham com o conceito de produtores, consumidores e eventos trafegando por tópicos ou filas.&lt;/p&gt;

&lt;p&gt;E aqui começa a parte interessante para nós, QAs. 👀&lt;/p&gt;




&lt;h2&gt;
  
  
  🔄 Event Streaming vs API
&lt;/h2&gt;

&lt;p&gt;Você pode se perguntar:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Alicia, mas qual a diferença disso para uma API? Eu consigo fazer o mesmo apenas chamando uma API.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Conseguir, você consegue. Mas será que é a melhor opção? 🤔&lt;/p&gt;

&lt;p&gt;Depende.&lt;/p&gt;

&lt;p&gt;Normalmente, sistemas que utilizam apenas APIs (geralmente síncronas) funcionam assim: fazem uma requisição, &lt;strong&gt;esperam a resposta&lt;/strong&gt;, e só depois continuam o processamento. Isso pode deixar o sistema mais acoplado e dependente.&lt;/p&gt;

&lt;p&gt;Já em um sistema de &lt;strong&gt;Event Streaming (assíncrono)&lt;/strong&gt;, não é necessário esperar uma resposta para continuar o fluxo. O sistema segue funcionando enquanto os eventos são processados em paralelo.&lt;/p&gt;

&lt;p&gt;E, principalmente, não queremos que o usuário fique esperando uma resposta para continuar usando o software ⏳.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧪 E onde o QA e automação entram nisso?
&lt;/h2&gt;

&lt;p&gt;Quando falamos de sistemas orientados a eventos, surgem novas perguntas de teste:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;O evento foi realmente publicado?&lt;/li&gt;
&lt;li&gt;O formato (schema) está correto?&lt;/li&gt;
&lt;li&gt;O consumidor está processando corretamente?&lt;/li&gt;
&lt;li&gt;O que acontece se o consumidor falhar?&lt;/li&gt;
&lt;li&gt;Existe retry?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Percebe como o nível de complexidade aumenta?&lt;/p&gt;

&lt;p&gt;Testar Event Streaming não é só validar payload. Envolve entender fluxo, consistência eventual, ordenação de mensagens, tolerância a falhas e comportamento assíncrono e partir dessa compreensão, é possível pensar em automações, do tipo: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Produzir eventos automatizados para testes&lt;/li&gt;
&lt;li&gt;Validar consumo de mensagens&lt;/li&gt;
&lt;li&gt;Testes de contrato (como com Schema Registry no Kafka)&lt;/li&gt;
&lt;li&gt;Testes de integração ponta a ponta&lt;/li&gt;
&lt;li&gt;Validação de mensagens em filas durante pipelines de CI/CD&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;Entender mensageria e Event Streaming não é apenas “coisa de backend” — é conhecimento essencial para qualquer QA que queira testar sistemas modernos com mais profundidade. 💡&lt;/p&gt;

&lt;p&gt;Quando compreendemos como os eventos trafegam, como os sistemas se comunicam de forma assíncrona e onde podem ocorrer falhas, ampliamos nossa capacidade de análise, criamos melhores cenários de teste e nos tornamos profissionais mais estratégicos.&lt;/p&gt;

&lt;p&gt;Escrever sobre isso em português é, para mim, uma forma de compartilhar conhecimento, fortalecer a comunidade e também me sentir mais próxima de casa. 💛&lt;/p&gt;

&lt;p&gt;E esse é só o começo. 🚀&lt;/p&gt;

</description>
      <category>testing</category>
      <category>braziliandevs</category>
      <category>beginners</category>
      <category>eventdriven</category>
    </item>
    <item>
      <title>Compartilhando seu conhecimento com o mundo! Como escrever artigos</title>
      <dc:creator>Cherry Ramatis</dc:creator>
      <pubDate>Mon, 13 Nov 2023 21:31:19 +0000</pubDate>
      <link>https://dev.to/he4rt/compartilhando-seu-conhecimento-com-o-mundo-como-escrever-artigos-5ghc</link>
      <guid>https://dev.to/he4rt/compartilhando-seu-conhecimento-com-o-mundo-como-escrever-artigos-5ghc</guid>
      <description>&lt;p&gt;Compartilhar conhecimento escrito é uma ótima forma de dominar um assunto específico, além de ser uma excelente maneira de melhorar a organização das ideias, comunicação e obviamente se autopromover na comunidade. Essa produção de artigos tanto técnicos quanto sociais são muito importantes tanto para quem escreve quanto para quem está lendo, lembre-se sempre: &lt;code&gt;Você sabe mais hoje do que quem começou ontem&lt;/code&gt;. Pensando nisso vamos discutir um pouco mais sobre como organizar as próprias ideias na produção de um artigo técnico para a plataforma dev.to!&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Método de Feynman e por que produzir conteúdo&lt;/li&gt;
&lt;li&gt;Coletando ideias e se motivando para escrever&lt;/li&gt;
&lt;li&gt;Entendendo a plataforma e achando a propria linguagem&lt;/li&gt;
&lt;li&gt;Aprendendo markdown e dicas gerais para uma boa formatação&lt;/li&gt;
&lt;li&gt;A estrutura básica de um artigo técnico&lt;/li&gt;
&lt;li&gt;Como revisar e melhorar a escrita&lt;/li&gt;
&lt;li&gt;Conclusão e agradecimentos&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Método de Feynman e por que produzir conteúdo
&lt;/h2&gt;

&lt;p&gt;Primeiramente precisamos discutir o porquê devemos compartilhar conteúdo, seja em formato textual (o foco desse artigo) ou em qualquer outro formato. Para iniciar essa discussão é importante entender o que é o método de Feynman e como ele pode nos ajudar a aprender 10 vezes mais com confiança e domínio do assunto.&lt;/p&gt;

&lt;p&gt;O método de Feynman foi criado por um físico muito importante chamado Richard Feynman visando desenvolver uma abordagem nova ao aprendizado, essa nova proposta assume uma verdade central: "Se você não consegue explicar algo de maneira clara e simples, então você ainda não entendeu completamente".&lt;/p&gt;

&lt;p&gt;Essa frase nos ajuda muito a pensar em como nosso aprendizado deve ser estruturado, pois a partir do momento que começamos a pensar em ensinar o que estamos estudando focamos muito mais em dominar as bases essenciais do assunto e se preparar para dúvidas que te forçam a olhar o problema, por outro lado, completamente diferente.&lt;/p&gt;

&lt;p&gt;Ao se preparar para situações como essas, o resultado claro é uma excelente confiança e domínio sobre o assunto em questão que estava sendo estudado.&lt;/p&gt;

&lt;p&gt;Eu particularmente amo esse método, o único problema dele é que quando saímos do ambiente acadêmico fica muito difícil de achar pessoas que se interessam pelo mesmo assunto que você esta estudando no momento, seja por não ter pessoas de TI no ciclo de amizade ou simplesmente por ter interesse em assuntos específicos. &lt;/p&gt;

&lt;p&gt;Para esse problema temos uma solução muito incrível chamada "Learning in public"! Essa prática consiste em compartilhar seu aprendizado online em comunidades de tecnologia, seja produzindo vídeos, fazendo live streams ou no objetivo desse artigo: escrevendo!&lt;/p&gt;

&lt;p&gt;Plataformas como o dev.to (que você usando para ler agora :D) visam tornar a ideia de "Learning in public" cada vez mais simples e próxima de quem está consumindo, pois agora é possível produzir artigos que vão chegar em pessoas com os mesmos interesses que os nossos que podem: aprender, tirar dúvidas ou mesmo sugerir mudanças e corrigir pensamentos. Incrível né?&lt;/p&gt;

&lt;h2&gt;
  
  
  Coletando ideias e se motivando para escrever
&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.amazonaws.com%2Fuploads%2Farticles%2Fec5y48jxgchcgz4e5vnf.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%2Fec5y48jxgchcgz4e5vnf.png" alt="writing meme" width="462" height="438"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;O processo de inspiração é provavelmente uma das fases mais chatinhas antes de produzir um artigo online, muitas vezes ficamos presos em um loop infinito de técnicas mirabolantes para ter ideias incríveis quando, na verdade, a solução acaba sendo muito simples: aceite suas ideias e consuma o máximo de conteúdos possível. &lt;/p&gt;

&lt;p&gt;A forma mais prática de encontrar ideias e de construir sua própria linguagem é ler o que as outras pessoas já produzem sobre os temas que lhe interessam, quer se trate de uma linguagem de programação, de um tema específico em TI, etc; esse consumo de conteúdo vem de diversas fontes diferentes como artigos técnicos, vídeos no YouTube, tweets da bolha Tech, discussões no Github e muitos outros locais possíveis.&lt;/p&gt;

&lt;p&gt;Bom, sei que falando assim parece simplificar algo que não é tão simples assim e eu concordo com você! Não é só ler ou assistir tudo o que existe na internet que vai nos tornar capazes de produzir os mesmos conteúdos, a habilidade mais importante que vai destacar essas pessoas é &lt;strong&gt;organizar as ideias que chegam no cérebro&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mantendo um segundo cérebro
&lt;/h3&gt;

&lt;p&gt;Nosso cérebro é uma excelente maquina de absorção de informação, praticamente uma esponja que guarda todas as informações ao nosso redor. O grande problema dessa maquina é que ela se tornou péssima em organização com o tempo, isso aconteceu principalmente para poupar energia já que não precisamos lembrar de tudo o tempo todo, mas sabendo disso o que podemos fazer para guardar as informações que queremos de forma organizada? Well well well jovem gafanhoto, precisamos parar de confiar no nosso cérebro é claro!&lt;/p&gt;

&lt;p&gt;Manter um &lt;em&gt;segundo cérebro&lt;/em&gt; é uma prática muito famosa entre escritores e pesquisadores, consiste em um local físico ou virtual onde você copia pequenos pedaços de conteúdo que consumiu junto de uma observação utilizando suas próprias palavras sobre o assunto. Esse amontoado de anotações vai compor o seu &lt;em&gt;segundo cérebro&lt;/em&gt; e vai te empoderar com a habilidade de achar qualquer conteúdo rapidamente e referenciar seus autores sem nunca esquecer nada!&lt;/p&gt;

&lt;p&gt;Para resumir a história, consuma o máximo de conteúdo possível, armazene-o em um segundo cérebro que pode ser armazenado e pesquisado e finalmente se desafie a escrever! Seja sobre um assunto que quer aprender, sobre algo específico que aprendeu recentemente ou até mesmo algo que domina há muitos anos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Entendendo a plataforma e achando a propria linguagem
&lt;/h2&gt;

&lt;p&gt;Entender a plataforma e o público que vamos atingir escrevendo nossos conteúdos é muito importante para podermos filtrar como vamos estruturar os artigos de forma geral certo? Na &lt;em&gt;minha opinião&lt;/em&gt;, o &lt;a href="https://dev.to"&gt;dev.to&lt;/a&gt; é uma plataforma bem informal que valoriza muito conteúdos no formato de tutoriais com um estilo conversacional e direto ao ponto, com essa informação podemos deduzir algumas formas de estruturar nossos artigos para que possamos ilustrar nossa ideia em um modelo conhecido pelos leitores.&lt;/p&gt;

&lt;p&gt;Isso significa que tudo o que você vai produzir são tutoriais diretos e informais?  De forma alguma! Só significa que você pode moldar o seu conteúdo para conter essa linguagem mais informal, conversacional e direta mesmo que o tema tratado seja super complexo, isso inclusive vira um desafio muito interessante de simplificar o complexo. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A habilidade de simplificar o complexo vai te acompanhar para o resto da sua vida profissional, é importantíssimo a criação de analogias e exemplos para que facilite o entendimento e a identificação com os problemas apresentados e as soluções propostas.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Aprendendo markdown e dicas gerais para uma boa formatação
&lt;/h2&gt;

&lt;p&gt;A forma para produzirmos nossos artigos no dev.to é utilizando uma linguagem de marcação (exatamente, igual o HTML) chamada &lt;a href="https://www.markdownguide.org" rel="noopener noreferrer"&gt;Markdown&lt;/a&gt;, apesar dela ser super simples é importante termos um domínio bem solido do que é possível fazer quando falamos sobre organizar e deixar nosso texto bonitinho, semelhante como vamos produzir uma estrutura complexa no Microsoft Word devemos ser capazes de produzir o mesmo com código Markdown.&lt;/p&gt;

&lt;p&gt;É sempre importante ressaltar a importância de um material educacional bem estruturado (afinal você está lendo esse artigo justamente para isso certo?) e quando o assunto é excelência e qualidade não consigo recomendar o suficiente o projeto &lt;a href="https://github.com/he4rt/4noobs" rel="noopener noreferrer"&gt;4noobs&lt;/a&gt; que junta em um único repositório diversos cursos grátis e no formato de texto sobre diversos assuntos em TI, para o tema desse artigo recomendo o uso do &lt;a href="https://github.com/jpaulohe4rt/markdown4noobs" rel="noopener noreferrer"&gt;markdown4noobs&lt;/a&gt; no aprendizado da linguagem de marcação Markdown.&lt;/p&gt;

&lt;h3&gt;
  
  
  O básico sobre manipulação de texto e blocos de código
&lt;/h3&gt;

&lt;p&gt;Markdown nos permite marcar partes do nosso texto com estruturas super básicas e necessárias como negrito, itálico, highlight, níveis de título, etc. Abaixo vamos ver rapidamente como realizar cada uma das ações com a sintaxe correta.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Primeiro titulo equivalente a um h1
## Segundo titulo equivalente a um h2
### Terceiro titulo equivalente a um h3
#### Quarto titulo equivalente a um h4

`Texto em highlight`
**Texto em negrito**
*Texto em itálico*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esses artifícios da linguagem markdown nos permitem controlar a narrativa a nosso favor e deixar a leitura mais simples de seguir, utilizando &lt;strong&gt;bold&lt;/strong&gt; no meio do texto para chamar a atenção, deixar explicito um &lt;code&gt;termo técnico&lt;/code&gt; usando highlight ou até mesmo utilizando imagens ilustrativas que introduzem o ponto do parágrafo enquanto mantém o clima geral do texto muito mais gostoso de ler.&lt;/p&gt;

&lt;p&gt;Outra coisa importante de se mencionar é o bloco especifico que usei acima, ele é extremamente útil quando vamos escrever artigos técnicos, tanto, pois ele permite um destaque maior a um bloco de texto quanto ele permite habilitar syntax highlighting caso esteja escrevendo um bloco de código, ele é utilizado da seguinte maneira:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Disclaimer: Como markdown não permite um bloco dentro de um bloco optei por mostrar com um screenshot:&lt;/p&gt;
&lt;/blockquote&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%2Fnpbqkad5a1cpuab3faau.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%2Fnpbqkad5a1cpuab3faau.png" alt="Bloco de codigo" width="650" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Após os símbolos de &lt;code&gt;backtick&lt;/code&gt;, podemos incluir o nome da linguagem (no meu caso ruby) para que o dev.to possa habilitar o syntax highlighting especifico para essa linguagem de programação.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tabela de conteúdo
&lt;/h3&gt;

&lt;p&gt;Caso o seu artigo esteja ultrapassando a margem das duas mil palavras ou esteja com pelo menos 4 títulos principais, eu recomendo fortemente que deixe definido um &lt;code&gt;Table of contents&lt;/code&gt;, ou &lt;code&gt;Tabela de conteúdo&lt;/code&gt;. Essa tabela de conteúdo serve para guiar a leitura durante os pontos principais que serão lidados durante o artigo, para a criação de um existem uns macetes que vou demonstrar abaixo:&lt;/p&gt;

&lt;h4&gt;
  
  
  Na plataforma dev.to, use listas não ordenadas ao invés de listas numeradas
&lt;/h4&gt;

&lt;p&gt;Listas em Markdown são bem simples de serem usadas e elas possuem dois tipos &lt;strong&gt;principais&lt;/strong&gt;: não ordenadas e numeradas.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- Uma lista
- Não
- Ordenada

1. Uma lista
2. Numerada
3. Aqui
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O problema de usar as listas numeradas no dev.to é que elas ficam desalinhadas como podemos ver no exemplo abaixo, portanto não recomendo o uso delas de maneira geral, sempre tento utilizar as listas não ordenadas e se for necessário aplicar alguma ordem utilizar um número após o símbolo da lista não ordenada manualmente.&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%2Fz2ss025hp6dzwy1rpw4j.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%2Fz2ss025hp6dzwy1rpw4j.png" alt="Listas no dev.to" width="749" height="485"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Como organizar os links para um título
&lt;/h4&gt;

&lt;p&gt;Assumindo que você já descobriu como estruturar um link no Markdown (já que você leu o markdown4noobs né?) vamos aprender os macetes simples para indicar um link em um título e como estruturar nossa tabela de conteúdo.&lt;/p&gt;

&lt;p&gt;Uma tabela de conteúdo de exemplo pode ser vista abaixo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;## Table of contents

- [What is metaprogramming anyway?](#what-is-metaprogramming-anyway)
- [In ruby everything is an object, what does that mean?](#in-ruby-everything-is-an-object-what-does-that-mean)
- [But what about rails? How this framework applies that concept for maximum developer experience](#but-what-about-rails-how-this-framework-applies-that-concept-for-maximum-developer-experience)
- [How to define methods dynamically](#how-to-define-methods-dynamically)
- [Using hooks to detect moments on the instantiation of the class](#using-hooks-to-detect-moments-on-the-instantiation-of-the-class)
- [Conclusion](#conclusion)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Como você pode ver, a ideia geral para definir a segunda parte do link é incluir uma hashtag &lt;code&gt;#&lt;/code&gt; junto ao título em um formato especifico seguindo as regras:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Trocar todos os espaços em branco por hifens &lt;code&gt;-&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;Deixar todo o título em minúsculo&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;E é isso! Títulos com acentos podem continuar igual sem nenhum problema, o Markdown compreende como texto padrão igual mostrado abaixo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- [Um título com muitos acentos e çedilha](#um-título-com-muitos-acentos-e-çedilha)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A estrutura básica de um artigo técnico
&lt;/h2&gt;

&lt;p&gt;Agora que temos uma noção interessante de como podemos marcar nosso texto para ficar legível e agradável de ler, vamos entender um pouco a estrutura de um artigo. É importante ressaltar que um modelo não vai servir para todo tipo de texto possível, a ideia é prover uma ideia geral que deve ser adaptada e mudada conforme o contexto.&lt;/p&gt;

&lt;p&gt;Primeiro é muito importante definir o parágrafo inicial para chamar o leitor para o problema ou a situação que você vai dissecar ao longo do artigo, é importante fazer isso, pois esse primeiro paragrafo vai ser usado pelo dev.to para comunicações de marketing por e-mail ou por redes sociais. Um exemplo de parágrafo inicial pode ser achado nesse mesmo artigo que você esta lendo ou em alguns outros que deixo abaixo:&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%2F95ps97f2imqyu7ootikc.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%2F95ps97f2imqyu7ootikc.png" alt="Paragrafo inicial exemplo 1" width="748" height="373"&gt;&lt;/a&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%2Fjpebk61g2cv73pbm974f.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%2Fjpebk61g2cv73pbm974f.png" alt="Paragrafo inicial exemplo 2" width="710" height="303"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A ideia é sempre utilizar perguntas e pausas no texto para podermos alcançar uma comunicação direta e bem conversacional, sempre tentando apresentar a situação da maneira mais geral possível para que quem esteja lendo fique com bastante curiosidade e vontade de ler.&lt;/p&gt;

&lt;p&gt;Após o primeiro parágrafo de apresentação, é muito importante definir a Tabela de conteúdo para guiar o usuário ao longo dos títulos principais do seu artigo, sobre isso inclusive eu particularmente não recomendo listar os subtítulos junto dos títulos por conta de deixar a tabela de conteúdo muito grande e pouco útil para quem estiver lendo, obviamente que se você considerar seus subtítulos muito importantes de serem listados é completamente valido incluir.&lt;/p&gt;

&lt;p&gt;Passando para o corpo geral do artigo, entramos em uma área muito subjetiva, pois depende muito do assunto que está sendo abordado para entender a estrutura dos seus títulos e parágrafos. Vou assumir um artigo no modelo de tutorial simples para ser possível partir de algum lugar.&lt;/p&gt;

&lt;p&gt;Recomendo sempre ter três &lt;em&gt;títulos satélites&lt;/em&gt; que vão nortear o seu artigo e dar inspiração para aumentar o conteúdo com mais detalhes. Esses títulos satélites são os seguintes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Introdução a tecnologia ou problema&lt;/code&gt;: Esse parágrafo vai servir para podermos detalhar o que foi falado no começo do artigo, respondendo às perguntas que criamos para atiçar a curiosidade e aprofundando mais no tema que vai ser discutido com os tópicos específicos.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Prós e contras&lt;/code&gt;: Nesse momento vamos deixar claro os prós e contras da solução que vai ser apresentada no artigo, seja ela uma arquitetura, um padrão de código, uma linguagem, framework, etc. A existência desse paragrafo pode ser bem situacional dependendo do seu tema, mas costuma ser bem útil caso esteja apresentando uma solução em forma de tutorial!&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Conclusão&lt;/code&gt;: Esse ponto é mais uma opinião do que uma regra geral, mas eu acho muito importante ter um parágrafo onde vai ser indicado o final do processo de leitura, assim podemos deixar argumentos finais, agradecimentos, contato e qualquer outra mensagem interessante.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ao redor desses três principais títulos, podemos desenvolver nosso artigo com uma escrita ilustrativa e que apresente exemplos práticos ou analogias, facilitando a visualização tanto do problema quanto da solução pelo leitor. É importante ressaltar também o cuidado ao aprofundar muito nas analogias, elas são extremamente uteis, mas podem ser um tiro no pé quando você abusa e nunca volta para o mundo real com uma solução e explicação clara.&lt;/p&gt;

&lt;p&gt;Uma dica geral ao redor da estrutura do artigo é manter o sentimento geral da leitura leve, portanto é super aconselhável utilizar imagens (seja um meme para descontrair ou um gráfico para ilustrar melhor o ponto apresentado), como a plataforma do dev.to comporta artigos técnicos mais informais, abusar dessa linguagem mais próxima é uma estratégia muito certeira.&lt;/p&gt;

&lt;h2&gt;
  
  
  Como revisar e melhorar a escrita
&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.amazonaws.com%2Fuploads%2Farticles%2Ffca2sx1frbz29241ne8j.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%2Ffca2sx1frbz29241ne8j.png" alt="code review meme" width="572" height="459"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Bom, agora que temos uma boa ideia de como estruturar nosso artigo, como mantê-lo bonito utilizando Markdown e como ponderar nossa linguagem para a plataforma especifica, o que falta? Bom, agora o que falta é entender que não somos perfeitos e erramos, portanto, precisamos de uma boa estratégia para revisar o artigo que acabamos de produzir com nossas técnicas aprendidas.&lt;/p&gt;

&lt;p&gt;Para auxiliar na escrita, eu recomendo fortemente utilizar um editor que oferece visualização do Markdown em tempo real como &lt;a href="https://code.visualstudio.com/Docs/languages/markdown" rel="noopener noreferrer"&gt;VSCode&lt;/a&gt; ou o queridinho da comunidade &lt;a href="https://obsidian.md" rel="noopener noreferrer"&gt;Obsidian&lt;/a&gt;. Inclusive esse artigo foi escrito utilizando o Obsidian!&lt;/p&gt;

&lt;p&gt;No que se entende por revisão temos algumas ferramentas bem interessantes que nos auxiliam em diversos aspectos da escrita:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://languagetool.org" rel="noopener noreferrer"&gt;LanguageTool&lt;/a&gt;: Essa ferramenta é meu xodózinho e ela cuida de toda correção de ortografia, a parte legal é que nessa ferramenta você pode ter dicas de contexto que vão melhorar a frase e também correção de termos específicos de programação como nomes de linguagens visto que o banco de dados deles é super atualizado.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.deepl.com/translator" rel="noopener noreferrer"&gt;Deepl&lt;/a&gt;: Entrando no mundo das inteligências artificiais, o Deepl utiliza deep learning para oferecer uma interface incrível de tradução, mas não só isso! Com ele podemos ter uma segunda opinião para refrasear nosso parágrafo de forma muito simples, você só precisa traduzir o texto para inglês e traduzir o texto em inglês para português de novo; normalmente no Google Translate isso destruiria o contexto, mas essa ferramenta mantém o contexto e melhora as expressões para que você possua uma segundo percepção de um mesmo paragrafo.&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://chat.openai.com" rel="noopener noreferrer"&gt;ChatGPT&lt;/a&gt; ou &lt;a href="https://bard.google.com" rel="noopener noreferrer"&gt;Bard&lt;/a&gt;: Bom, aqui eu confesso que não tenho muito domínio e não uso tanto, mas essas interfaces de chat com IA ajudam muito a ter visões diferentes para refrasear um parágrafo já existente ou até mesmo começar a escrever algum. &lt;strong&gt;Importante: Preciso ressaltar para usar essas ferramentas apenas para ter ideias ou ajudar a refrasear textos que você já escreveu, por favor não produza artigos inteiros utilizando inteligência artificial&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://heartdevs.com" rel="noopener noreferrer"&gt;Comunidade&lt;/a&gt;: Na comunidade He4rt Developers tentamos providenciar o máximo de auxílio para a escrita de artigos técnicos na plataforma dev.to. Fazemos isso providenciando um fórum onde você pode postar seu artigo ainda em progresso e ganhar feedback da comunidade até a publicação, após a publicação a gente ainda faz um trabalho de divulgação para pessoas ativas! &lt;strong&gt;Disclaimer: Obviamente estou citando a He4rt, pois temos um projeto voltado para isso, mas a lição geral é compartilhar seus progressos com a comunidade de maneira geral.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Esse é o último artigo que estou postando seguindo o desafio &lt;a href="https://www.100diasdecodigo.dev" rel="noopener noreferrer"&gt;100 dias de código&lt;/a&gt;, foi um desafio muito intenso e com muito aprendizado onde eu descobri uma nova paixão: escrever e compartilhar conhecimento! Não tenho nem palavras para agradecer a comunidade da He4rt por ter me apoiado 100% nessa jornada imensa. Espero que esse artigo seja útil para quem esteja lendo e que inspire qualquer um a compartilhar conhecimento online para podermos criar uma internet mais segura e rica em informação. &lt;/p&gt;

&lt;p&gt;Gostaria também de deixar um agradecimento especial aos revisores desse artigo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/anibalsolon" rel="noopener noreferrer"&gt;Anibal Sólon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/m4rri4nne" rel="noopener noreferrer"&gt;Alicia Marianne&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/m1guelsb" rel="noopener noreferrer"&gt;Miguel S. Barbosa&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Clintonrocha98" rel="noopener noreferrer"&gt;Clinton Rocha&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/SamucaDev" rel="noopener noreferrer"&gt;Samuel Rodrigues&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;May the force be with you! 🍒&lt;/p&gt;

</description>
      <category>writing</category>
      <category>beginners</category>
      <category>braziliandevs</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Compondo música com Java! É hora do Jmusic</title>
      <dc:creator>Bruno Rezende Novais</dc:creator>
      <pubDate>Tue, 24 Oct 2023 11:56:13 +0000</pubDate>
      <link>https://dev.to/he4rt/compondo-musica-com-java-e-hora-do-jmusic-db</link>
      <guid>https://dev.to/he4rt/compondo-musica-com-java-e-hora-do-jmusic-db</guid>
      <description>&lt;p&gt;É isso mesmo que você leu, se você sempre quis compor uma música ou imaginou como fazer isso com código saiba que é possível fazer isso e está a poucas linhas de distância. Hoje vou discutir um pouco e mostrar como é possível &lt;em&gt;codar&lt;/em&gt; música usando Java.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Índice&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;O que é Música Computacional?&lt;/li&gt;
&lt;li&gt;JMusic&lt;/li&gt;
&lt;li&gt;Show me The Music&lt;/li&gt;
&lt;li&gt;Quais as vantagens de se compor usando código?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a id="musica-computacional"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  O que é Música Computacional?
&lt;/h3&gt;

&lt;p&gt;Primeiro é necessário darmos um passo atrás e falarmos sobre &lt;strong&gt;música computacional&lt;/strong&gt;, para quem não conhece, de forma geral música computacional pode ser definida como &lt;em&gt;música que envolve computação em qualquer estágio de seu ciclo de vida&lt;/em&gt; (Collins, 2009). Se aprofundarmos um pouco mais nessa definição, podemos pensar esse &lt;em&gt;ramo como o estudo do uso da tecnologia computacional na música em quaisquer de seus estágios, seja na teoria ou prática.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Hoje com o avanço das inteligências artificiais a composição de música por máquinas já não é algo tão distante e geralmente não percebemos, mas já há o uso desse ramo de estudo em nosso dia a dia. Seja no &lt;strong&gt;Spotify&lt;/strong&gt; que você abre, nos módulos de equalização de som que há no seu sistema operacional ou até mesmo no estúdio quando o engenheiro de som está usando compressores digitais ou efeitos de reverbs criados virtualmente. A música computacional é uma realidade. &lt;/p&gt;

&lt;p&gt;Se ela é algo ético ou não, podemos discutir isso em outro artigo, mas hoje vamos passar a aprender um pouco mais sobre.&lt;/p&gt;

&lt;p&gt;&lt;a id="jmusic"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  JMusic
&lt;/h3&gt;

&lt;p&gt;Saiba que há uma infinidade de bibliotecas criadas para manipular e gerar som em diversas linguagens de programação, mas nesse artigo irei focar e mostrar que é possível criar música com &lt;strong&gt;Java&lt;/strong&gt;. Para isso, vou introduzir a biblioteca &lt;strong&gt;JMusic&lt;/strong&gt;, um projeto criado por &lt;strong&gt;Andrew Sorensen&lt;/strong&gt; e &lt;strong&gt;Andrew R. Brown&lt;/strong&gt;. Trata-se de uma lib completa e focada em composição que agrega desde exportadores de arquivo MIDI até mesmo instrumentos virtuais para você criar suas composições. Vale citar aqui também o livro escrito por Andrew R. Brown que foca exatamente na composição usando Java, &lt;a href="https://www.amazon.com/Making-Music-Java-Andrew-Brown/dp/1409281337" rel="noopener noreferrer"&gt;vale leitura!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A documentação do JMusic está disponível &lt;a href="https://explodingart.com/jmusic/" rel="noopener noreferrer"&gt;aqui.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a id="show-me-the-music"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Show Me The Music
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.discordapp.net%2Fattachments%2F1164356280643760169%2F1164889726076059658%2Fimage.png%3Fex%3D6544db19%26is%3D65326619%26hm%3Dcca2b921c8db4bebd5234fee7569b0e7636cf6befcbad67689c3d1fb7831e719%26%3D%26width%3D1813%26height%3D857" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.discordapp.net%2Fattachments%2F1164356280643760169%2F1164889726076059658%2Fimage.png%3Fex%3D6544db19%26is%3D65326619%26hm%3Dcca2b921c8db4bebd5234fee7569b0e7636cf6befcbad67689c3d1fb7831e719%26%3D%26width%3D1813%26height%3D857" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Vamos dar uma olhada no código e analisá-lo tal como um maestro. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Caso não esteja familiarizado com alguns termos musicais vou deixar um glossário no fim do artigo para ficar mais fácil :)&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;jm.constants.ProgramChanges&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;jm.music.data.Note&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;jm.music.data.Part&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;jm.music.data.Phrase&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;jm.music.data.Score&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;jm.util.Read&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;jm.util.Write&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;static&lt;/span&gt; &lt;span class="n"&gt;jm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;constants&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Durations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SEMIBREVE&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;static&lt;/span&gt; &lt;span class="n"&gt;jm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;constants&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Pitches&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;C4&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="nc"&gt;Score&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Score&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"JMDemo1 - Chromatic Scale"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;Part&lt;/span&gt; &lt;span class="n"&gt;prt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Part&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Piano"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ProgramChanges&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;PIANO&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;Phrase&lt;/span&gt; &lt;span class="n"&gt;phr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Phrase&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Chromatic Scale"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;Note&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Note&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;C4&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="no"&gt;SEMIBREVE&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;phr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;prt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phr&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prt&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;SimpleSineInst&lt;/span&gt; &lt;span class="n"&gt;simpleSineInst&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SimpleSineInst&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;Write&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;au&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"WaveformExample.au"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="n"&gt;simpleSineInst&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;audio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Read&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;audio&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"WaveformExample.au"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;Write&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;audio&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;audio&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="s"&gt;"waveform.wav"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;



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

&lt;/div&gt;



&lt;h3&gt;
  
  
  3.1 Definindo a partitura
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;
      &lt;span class="nc"&gt;Score&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Score&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"JMDemo1 - Chromatic Scale"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
      &lt;span class="nc"&gt;Part&lt;/span&gt; &lt;span class="n"&gt;prt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Part&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Piano"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ProgramChanges&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;PIANO&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
      &lt;span class="nc"&gt;Phrase&lt;/span&gt; &lt;span class="n"&gt;phr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Phrase&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Chromatic Scale"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Primeiro instanciamos uma classe chamada Score (partitura) e precisaremos definir algumas partes dela como qual será o instrumento a seguir e qual canal do arquivo MIDI utilizaremos. Pense que o arquivo &lt;strong&gt;MIDI&lt;/strong&gt; (Musical Instrument Digital Interface) é um arquivo que tem várias linhas ou canais. &lt;/p&gt;

&lt;p&gt;A partir daí, já na linha seguinte criamos uma frase, que é onde conterá nossas notas e ritmos. Nesse caso, vamos fazer um exemplo mais simples e criar uma escala cromática começando a partir do minuto 0:0.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;
 &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;Note&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Note&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;C4&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="no"&gt;SEMIBREVE&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;phr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Já no laço for, inserimos dentro da nossa frase as notas, a nota por sua vez tem também a sua altura. (usaremos a nota Dó – C na cifra americana) Adotaremos o Dó  e como ritmo usaremos a semibreve que é uma unidade de tempo para a música. Assim, incrementalmente passaremos de nota em nota até completarmos o laço.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;
 &lt;span class="n"&gt;prt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phr&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

 &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prt&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Podemos pensar então que um Score (ou partitura) contém partes e as partes contém as frases que por sua vez contém as notas. Dessa forma vamos adicionando-as até que a frase fique completa.&lt;/p&gt;

&lt;h4&gt;
  
  
  3.2. Sintetizando nossa partitura
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;SimpleSineInst&lt;/span&gt; &lt;span class="n"&gt;simpleSineInst&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SimpleSineInst&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;Write&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;au&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"WaveformExample.au"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="n"&gt;simpleSineInst&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Poderíamos já tocar nossa composição, porém vamos exportá-la primeiro. Para isso, vamos ter que usar um sintetizador para produzir os sons da nossa partitura, assim criaremos uma instância de SimpleSineInst (um sintetizador simples) e passaremos ele para o módulo de escrita, exportando-o inicialmente em um arquivo do tipo au.&lt;/p&gt;

&lt;h4&gt;
  
  
  3.3 Exportando em outros formatos
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;
&lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;audio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Read&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;audio&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"WaveformExample.au"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;Write&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;audio&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;audio&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="s"&gt;"waveform.wav"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Tendo agora nosso &lt;code&gt;arquivo.au&lt;/code&gt;fica fácil, basta que leiamos ele e em seguida pode-se escrevê-lo como arquivo de áudio na extensão .wav por exemplo que é mais comum. Agora temos nossa composição exportada!&lt;/p&gt;

&lt;p&gt;Ouça &lt;a href="https://on.soundcloud.com/oVMvd" rel="noopener noreferrer"&gt;aqui&lt;/a&gt; o resultado final! Mas antes, &lt;strong&gt;recomendo que abaixe o volume de seu fone de ouvido&lt;/strong&gt; para evitar problemas.&lt;/p&gt;

&lt;p&gt;&lt;a id="quais-as-vantagens"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Quais as vantagens de se compor usando código?
&lt;/h3&gt;

&lt;p&gt;Bom, a primeira de todas é que compomos uma música e criamos um &lt;em&gt;sample&lt;/em&gt;(amostra) usando &lt;strong&gt;apenas&lt;/strong&gt; código. Sim, não tivemos que comprar um teclado, uma guitarra ou uma interface de áudio para podermos capturar o som, bastou apenas algumas linhas do bom e velho Java. Com isso a primeira vantagem é custo, &lt;strong&gt;o custo para criar música assim é bem reduzido.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ainda assim, podemos dar uma extrapolada e ir para “fora da caixa”. Podemos pensar no uso disso em diversos lugares, como por exemplo: games. Pode-se &lt;em&gt;gerar proceduralmente música&lt;/em&gt;, pode-se fazer com que a partir de uma determinada decisão do jogador um som seja gerado ou ele mesmo gere uma partitura e no final você toque-a usando o JMusic por baixo dos panos, pode-se ler uma imagem por exemplo e convertê-la em uma sequência numérica para poder criar música a partir disso, &lt;strong&gt;música a partir de uma imagem&lt;/strong&gt;! &lt;/p&gt;

&lt;p&gt;Em termos de &lt;strong&gt;acessibilidade&lt;/strong&gt;, podemos agora dar o poder para queles que não conseguem tocar por suas limitações físicas para dentro do mundo virtual, onde conseguirão produzir música usando os recursos de assistência dos computadores.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Aqui, o que limita é apenas a sua criatividade e a sua habilidade no código.&lt;/em&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  Bibliografia
&lt;/h5&gt;

&lt;p&gt;COLLINS,N. &lt;strong&gt;Introduction to Computer Music&lt;/strong&gt;. West Sussex, Reino Unido. 2009. p 2&lt;/p&gt;

&lt;p&gt;&lt;a id="glossario"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  Glossário
&lt;/h5&gt;

&lt;p&gt;&lt;a id="partitura"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Partitura&lt;/strong&gt;: Paritura pode ser definida como uma representação escrita de uma música onde está definido o ritmo, o tempo e as frases da música.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a id="frase"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Frase&lt;/strong&gt;: Também conhecida como frase musical, pode ser definido como um trecho de música coerente e autonomo em relação a um ritmo definido pela partitura.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a id="semibreve"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Semibreve&lt;/strong&gt;: Semibreve é uma das figuras rítmicas usadas na música sendo ela uma das de maior duração. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a id="escala-cromatica"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Escala cromática&lt;/strong&gt;: A escala cromática é uma das escalas na música, escalas nada mais são que uma sequência ordenada de notas. Você pode saber mais sobre essa escala &lt;a href="https://www.descomplicandoamusica.com/escala-cromatica/" rel="noopener noreferrer"&gt;neste link&lt;/a&gt;. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a id="au"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Formato de arquivo Au&lt;/strong&gt;:  arquivos &lt;code&gt;.au&lt;/code&gt; são tipo um tipo voltado para áudio,  menos usual porém focado em conter informações os dados de áudio divido em três grandes blocos de dados.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a id="wav"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Formato de arquivo WAV&lt;/strong&gt;: arquivos &lt;code&gt;.wav&lt;/code&gt; são um tipo mais comum de arquivos de áudio, geralmente usados já para músicas completas com uma maior qualidade sonora, geralmente são arquivos mais pesados em termos de espaço  (megabytes)&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>java</category>
      <category>computacional</category>
      <category>music</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Conhecendo o ecossistema Crystal: ferramentas e projetos</title>
      <dc:creator>guto</dc:creator>
      <pubDate>Mon, 16 Oct 2023 23:26:52 +0000</pubDate>
      <link>https://dev.to/he4rt/conhecendo-o-ecossistema-crystal-ferramentas-e-projetos-49m1</link>
      <guid>https://dev.to/he4rt/conhecendo-o-ecossistema-crystal-ferramentas-e-projetos-49m1</guid>
      <description>&lt;p&gt;Bem, se você abriu esse artigo, então, tenho certeza que está interessado em entender um pouco mais sobre o ecossistema de Crystal. Antes de mais nada, vamos primeiramente introduzir o que é Crystal e onde se aplica.&lt;/p&gt;




&lt;h2&gt;
  
  
  Índice
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;O que é Crystal?&lt;/li&gt;
&lt;li&gt;Ruby e Crystal&lt;/li&gt;
&lt;li&gt;Integração nativa com LibC&lt;/li&gt;
&lt;li&gt;
Ferramentas

&lt;ol&gt;
&lt;li&gt;Language Server Protocol (LSP)&lt;/li&gt;
&lt;li&gt;Read-Eval-Print Loop (REPL)&lt;/li&gt;
&lt;li&gt;Code style linter&lt;/li&gt;
&lt;li&gt;Debugger&lt;/li&gt;
&lt;li&gt;Gerenciador de dependências&lt;/li&gt;
&lt;li&gt;Livro&lt;/li&gt;
&lt;li&gt;Documentação&lt;/li&gt;
&lt;li&gt;Patterns&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;
&lt;li&gt;
Frameworks

&lt;ol&gt;
&lt;li&gt;Kemal&lt;/li&gt;
&lt;li&gt;Amber&lt;/li&gt;
&lt;li&gt;Lucky&lt;/li&gt;
&lt;li&gt;Marten&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;
&lt;li&gt;Projetos com Crystal&lt;/li&gt;
&lt;li&gt;Finalização&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  O que é Crystal?
&lt;/h2&gt;

&lt;p&gt;Crystal é uma linguagem de programação de código aberto, com sintaxe inspirada no Ruby, que visa a produtividade e o desempenho. Ele tem uma sintaxe elegante e uma tipagem estática, mas sem a necessidade de declarar tipos de variáveis. O Crystal é compilado para código nativo, e não para bytecode, e usa LLVM para gerar o código binário.&lt;/p&gt;

&lt;p&gt;A aplicabilidade de Crystal é vasta, podendo ser visto de diferentes formas, mas, consigo afirmar que a ênfase de aplicabilidade ocorre para aplicações web, na qual consegue se destacar em pontos específicos exatamente por ser uma linguagem de programação compilada.&lt;/p&gt;

&lt;p&gt;O principal motivo para o surgimento de Crystal foi para resolver alguns problemas bem específicos que Ruby possuia, porém, seriam solucionados deixando de lado a dinamicidade que Ruby proporcionava para abrir espaço para uma linguagem de programação totalmente nova, com a possibilidade de inferência de tipos, sendo compilada. Assim, em 2011 surge o projeto de uma nova linguagem de programação, criada por &lt;a href="https://github.com/asterite"&gt;Ary Borenszweig&lt;/a&gt;, &lt;a href="https://github.com/waj"&gt;Juan Wajnerman&lt;/a&gt; e &lt;a href="https://github.com/bcardiff"&gt;Brian Cardiff&lt;/a&gt;, contando hoje com mais de 450 colaboradores do mundo todo, tendo seu código fonte disponibilizado no repositório oficial do GitHub.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ruby e Crystal
&lt;/h2&gt;

&lt;p&gt;Como citado anteriormente, Ruby por sua vez possui uma sintaxe muito elegante, na qual Crystal se inspirou em sua maior parte exatamente pelo contexto de desenvolvimento da época, desejando uma linguagem de programação elegante na sintaxe como Ruby, veloz como C, podendo ainda realizar otimizações nativas com interoperabilidade utilizando a LibC e o melhor: sem escrever uma linha de código em C. Parece mágica não é? Bem, é apenas Crystal!&lt;/p&gt;

&lt;p&gt;Uma analogia para entender melhor a compatibilidade de ambas as linguagens de programação que gosto de fazer é: imagine que você hoje é dono de uma empresa que possui N desenvolvedores Ruby, que consequentemente são a maioria dentro da empresa. Em um belo dia você em uma conversa com seus arquitetos de software decidem que uma linguagem compilada é necessária em apenas alguns serviços que estão funcionando hoje. Ao invés de simplesmente reescrever todo o código em alguma outra linguagem compilada, você pode reaproveitar toda a base de código para utilizar e compilar em Crystal, assim, a curva de aprendizado para seus desenvolvedores Ruby será praticamente nula, afinal, apenas alguns pontos específicos requerem maior cuidado ao se trabalhar com Crystal, como por exemplo a verificação de tipos, que pode ser um pouco estranho no início, mas, garanto que com o passar do tempo será mais prática a manutenção de seu código.&lt;/p&gt;

&lt;p&gt;Entenda que Crystal não veio &lt;em&gt;para substituir o Ruby&lt;/em&gt;, mas, para que ambos possam ser utilizados em conjunto, visando um melhor aproveitamento de código, melhor eficiência e melhor ambiente de desenvolvimento. O exemplo que deixarei abaixo demonstra a diferença de dois códigos que fazem a mesma coisa, porém, Crystal possui uma peculiaridade específica que vou retratar logo abaixo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;soma&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s2"&gt;"Digite o primeiro número inteiro: "&lt;/span&gt;
&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;gets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chomp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s2"&gt;"Digite o segundo número inteiro: "&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;gets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chomp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"A soma dos números é &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;soma&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O código acima é bem simples: pedimos para o usuário informar dois números, &lt;code&gt;a&lt;/code&gt; e &lt;code&gt;b&lt;/code&gt; respectivamente, na qual &lt;code&gt;chomp&lt;/code&gt; remove o enter (&lt;code&gt;\n&lt;/code&gt; ou &lt;code&gt;\r&lt;/code&gt;) e o &lt;code&gt;to_i&lt;/code&gt; converte o valor para inteiro. Agora, em Crystal precisamos apenas adicionar uma validação bem simples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;soma&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s2"&gt;"Digite o primeiro número inteiro: "&lt;/span&gt;
&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;gets&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s2"&gt;"Digite o segundo número inteiro: "&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;gets&lt;/span&gt;

&lt;span class="c1"&gt;# É necessário validar se o valor é nil!&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nil?&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nil?&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Você deve digitar dois números inteiros."&lt;/span&gt;
  &lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"A soma dos números é &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;soma&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chomp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chomp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A validação é necessária para não enviarmos um valor nulo para o método &lt;code&gt;chomp&lt;/code&gt;, afinal, ele está esperando receber uma &lt;code&gt;String&lt;/code&gt; e sem a validação estaremos enviando um &lt;code&gt;String | Nil&lt;/code&gt;, não validando com o tipo esperado. Nesse momento você deve estar pensando: &lt;em&gt;mas, os tipos enviados foram validados em tempo de compilação? Isto é, a inferência dos tipos foi feita sem eu precisar falar nada&lt;/em&gt;? Bem, a resposta é &lt;strong&gt;sim&lt;/strong&gt;, o compilador automaticamente &lt;em&gt;adivinhou&lt;/em&gt; de quais tipos se tratavam.&lt;/p&gt;

&lt;p&gt;Certo, agora você deve perguntar: &lt;em&gt;nossa mas eu também consigo inferir os tipos manualmente? Como por exemplo na função &lt;code&gt;soma&lt;/code&gt;, informando os tipos dos parâmetros que espero receber, além do tipo do retorno&lt;/em&gt;? A resposta é &lt;strong&gt;sim&lt;/strong&gt;, e neste caso ficaria algo como o exemplo abaixo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;soma&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;Int32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;Int32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;Int32&lt;/span&gt;
  &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Interessante, certo?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;O principal ponto é dar nas mãos do desenvolvedor a possibilidade de &lt;em&gt;nivelar&lt;/em&gt; exatamente o que um método deve ter e/ou deve retornar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integração nativa com LibC
&lt;/h2&gt;

&lt;p&gt;Se você quer utilizar ferramentas da LibC, mas, sem a necessidade de escrever código em C, então Crystal pode te ajudar com isso. Com bindings nativas você pode utilizar bibliotecas externas, incluindo a LibC, para utilizar ferramentas que seriam possíveis apenas escrevendo código nativo em C.&lt;/p&gt;

&lt;p&gt;Para entender um pouco melhor sobre o funcionamento, recomendo seguir a &lt;a href="https://crystal-lang.org/reference/1.10/syntax_and_semantics/c_bindings/lib.html"&gt;documentação oficial&lt;/a&gt;, porém, deixarei um exemplo abaixo,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="k"&gt;lib&lt;/span&gt; &lt;span class="no"&gt;C&lt;/span&gt;
  &lt;span class="c1"&gt;# Em C: double cos(double x)&lt;/span&gt;
  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;Float64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;Float64&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;O exemplo acima demonstra o uso da função &lt;code&gt;cos&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Ferramentas
&lt;/h2&gt;

&lt;p&gt;Como todo desenvolvedor, nada melhor do que saber como preparar seu ambiente de desenvolvimento com Crystal, não é mesmo? Bem, vamos então navegar por algumas tecnologias que vão te ajudar nessa jornada.&lt;/p&gt;

&lt;h3&gt;
  
  
  Language Server Protocol (LSP)
&lt;/h3&gt;

&lt;p&gt;Como todo bom desenvolvedor, sabemos que um LSP pode nos auxiliar muito durante o desenvolvimento de software, por isso, o LSP que estarei recomendando para uso com Crystal é o &lt;a href="https://github.com/elbywan/crystalline"&gt;Crystalline&lt;/a&gt;, na qual você poderá configurar de forma simples na maioria dos editores de código que esteja utilizando. &lt;/p&gt;

&lt;p&gt;Basicamente o Crystalline vai disponibilizar um executável simples, sendo totalmente escrito em Crystal. Você pode seguir os passos deixados no próprio repositório para configurar seu ambiente, porém, gostaria de dizer que um dos guias de configuração que contribuí foi para o Emacs, utilizando o &lt;a href="https://emacs-lsp.github.io/lsp-mode/tutorials/crystal-guide/"&gt;emacs-lsp&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Minhas recomendações para editores de código são bem pessoais, porém, recomendaria alguns como Visual Studio Code, Sublime Text, Vim e Emacs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Read-Eval-Print Loop (REPL)
&lt;/h3&gt;

&lt;p&gt;Desenvolver ao lado de um REPL é uma experiência simplesmente magnífica e recomendo sempre que possível, por isso, Crystal não fica de fora no mundo de REPLs e possui o &lt;a href="https://github.com/crystal-community/icr"&gt;icr&lt;/a&gt; como REPL oficial, vindo hoje já embutido no próprio Crystal, podendo ser acessado com o comando &lt;code&gt;crystal i&lt;/code&gt;!&lt;/p&gt;

&lt;p&gt;Basicamente a funcionalidade principal do REPL é ser seu companheiro durante o desenvolvimento, possibilitando a execução de trechos do código de forma isolada sem muitos problemas.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code style linter
&lt;/h3&gt;

&lt;p&gt;Mais importante do que escrever código para si mesmo é escrever código para outras pessoas, por isso, sempre que possível é recomendado escrever códigos de acordo com as convenções de cada linguagem de programação. Dessa forma, Crystal possui uma &lt;a href="https://crystal-lang.org/reference/1.10/conventions/coding_style.html"&gt;convenção de estilo de código&lt;/a&gt; incrível, logo, nada mais justo do que uma ferramenta que te ajude no processo de escrita.&lt;/p&gt;

&lt;p&gt;Assim surge o projeto &lt;a href="https://github.com/crystal-ameba/ameba"&gt;Ameba&lt;/a&gt;, sendo um linter para validar seu código e verificar se está adequado de acordo com as convenções apresentadas oficialmente.&lt;/p&gt;

&lt;h3&gt;
  
  
  Debugger
&lt;/h3&gt;

&lt;p&gt;Nada melhor do que poder rodar seu código passo por passo para validar problemas e rotinas, não é? Pois bem, o projeto &lt;a href="https://github.com/Sija/debug.cr"&gt;debug&lt;/a&gt; surge com objetivo de ser um debugger completo para seu desenvolvimento, sendo escrito completamente em Crystal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gerenciador de dependências
&lt;/h3&gt;

&lt;p&gt;Dependências podem ser definidas como um conjunto de pacotes/bibliotecas que são necessárias para a execução de determinada aplicação. Gerenciar dependências de forma elegante é algo que Crystal não decepciona utilizando o projeto &lt;a href="https://github.com/crystal-lang/shards"&gt;Shards&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Basicamente existe um arquivo chamado &lt;code&gt;shard.yml&lt;/code&gt;, utilizado para definir de maneira simples pontos como nome do projeto, autores, dependências, versão atual e licença em que determinado projeto é distribuído.&lt;/p&gt;

&lt;h3&gt;
  
  
  Livro
&lt;/h3&gt;

&lt;p&gt;Um livro sobre a linguagem é necessário, não apenas para ser utilizado como referência, mas, para ser utilizado no processo de aprendizado do leitor.&lt;/p&gt;

&lt;p&gt;Assim surge o projeto &lt;a href="https://github.com/crystal-lang/crystal-book"&gt;crystal-book&lt;/a&gt;, sendo um livro completo com referências da documentação oficial.&lt;/p&gt;

&lt;h3&gt;
  
  
  Documentação
&lt;/h3&gt;

&lt;p&gt;A documentação oficial é necessária para qualquer tecnologia, assim, Crystal possui dois tipos de documentações que podem ser encontradas, sendo elas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://crystal-lang.org/reference/1.10/index.html"&gt;Documentação do site oficial com o Crystal Book&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://crystal-lang.org/api/1.10.1/"&gt;Documentação de &lt;em&gt;API&lt;/em&gt; da versão atual&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A segunda referência geralmente é utilizada para um detalhamento maior dos métodos e funcionalidades nativas que Crystal proporciona.&lt;/p&gt;

&lt;h3&gt;
  
  
  Patterns
&lt;/h3&gt;

&lt;p&gt;A definição de patterns e padrões de código é necessária, por isso, existe um repositório com exemplos didáticos de implementações com Crystal utilizando os patterns GOF (Gang of Four), chamado &lt;a href="https://github.com/crystal-community/crystal-patterns"&gt;crystal-patterns&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frameworks
&lt;/h2&gt;

&lt;p&gt;Atualmente, Crystal conta com diversos frameworks para diversos propósitos, porém, irei citar os mais conhecidos para desenvolvimento de aplicações web no geral.&lt;/p&gt;

&lt;h3&gt;
  
  
  Kemal
&lt;/h3&gt;

&lt;p&gt;Sendo um dos frameworks mais conhecidos, &lt;a href="https://github.com/kemalcr/kemal"&gt;Kemal&lt;/a&gt; se destaca por ser inspirado no estilo de outro framework já existente para Ruby, o Sinatra, sendo utilizado para ser uma solução simples, com tratamento de rotas elegante, possibilitando um middleware que pode ser ampliado.&lt;/p&gt;

&lt;p&gt;Sua implementação ocorre de forma simples, sendo bem minimalista, assim, uma comparação deixada no &lt;a href="https://kemalcr.com/"&gt;site oficial do Kemal&lt;/a&gt; compara a quantidade de requisições por segundo de uma aplicação com Kemal comparada com uma aplicação Ruby com Sinatra: enquanto o Sinatra conseguiu chegar em 5268 requisições por segundo, Kemal conseguiu atingir invejadas &lt;strong&gt;122697&lt;/strong&gt; requisições por segundo (magnífico, não é?).&lt;/p&gt;

&lt;h3&gt;
  
  
  Amber
&lt;/h3&gt;

&lt;p&gt;Com uma proposta um pouco diferente do Kemal, porém, levando tanto o Kemal quanto Rails e Phoenix como inspiração, temos o &lt;a href="https://github.com/amberframework/amber"&gt;Amber&lt;/a&gt;, um framework para aplicações robustas, possuindo templates de renderização HTML prontos, possibilitando um desenvolvimento mais produtivo.&lt;/p&gt;

&lt;p&gt;O framework conta com a possibilidade de uso de três ORMs distintos, sendo eles &lt;a href="https://github.com/imdrasil/jennifer.cr"&gt;Jennifer&lt;/a&gt;, &lt;a href="https://github.com/amberframework/granite"&gt;Granite&lt;/a&gt; e &lt;a href="https://github.com/Crecto/crecto"&gt;Crecto&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lucky
&lt;/h3&gt;

&lt;p&gt;Sendo um framework inspirado principalmente no Rails, o Lucky é um framework que está no coração de grande parte dos desenvolvedores que alguma vez já trabalharam com Crystal, trazendo infinitas possibilidades, sendo extremamente rápido e produtivo!&lt;/p&gt;

&lt;p&gt;O &lt;a href="https://github.com/luckyframework/lucky"&gt;repositório oficial&lt;/a&gt; está disponível no GitHub, tendo consigo também um artigo interessante falando sobre o uso do Lucky na perspectiva de um desenvolvedor que já trabalhou alguma vez com Rails. O nome do artigo é &lt;a href="https://hackernoon.com/ruby-on-rails-to-lucky-on-crystal-blazing-fast-fewer-bugs-and-even-more-fun-104010913fec"&gt;Ruby on Rails to Lucky on Crystal: Blazing fast, fewer bugs, and even more fun&lt;/a&gt; e recomendo a leitura.&lt;/p&gt;

&lt;h3&gt;
  
  
  Marten
&lt;/h3&gt;

&lt;p&gt;Um framework novo, porém com propostas interessantes, trazendo conceitos novos para o mundo de desenvolvimento com Crystal, ampliando a simplicidade e produtividade dentro do seu código. Este é o &lt;a href="https://github.com/martenframework/marten"&gt;Marten&lt;/a&gt;, tendo seu repositório oficial publicado no GitHub já está tomando espaço e crescendo cada vez mais como um dos frameworks para Crystal que tende a melhorar ainda mais. &lt;/p&gt;

&lt;p&gt;Já ganhou um espaço especial no meu coração e tenho certeza que você vai adorar utilizar o Marten em seu cotidiano como desenvolvedor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Projetos com Crystal
&lt;/h2&gt;

&lt;p&gt;Bem, como foi possível observar, a aplicabilidade de Crystal é bem vasta e existem diversas ferramentas para te auxiliar durante todo o processo, mas, quais projetos reais já existem e levam como base o uso de Crystal? Bem, vamos passar citando alguns maneiros se você &lt;em&gt;talvez&lt;/em&gt; esteja pensando em começar a contribuir...&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/veelenga/awesome-crystal"&gt;Awesome Crystal&lt;/a&gt;: conjunto de repositórios, bibliotecas e ferramentas para uso no desenvolvimento com Crystal, trazendo tudo de melhor que o ecossistema pode te proporcionar&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/iv-org/invidious"&gt;Invidious&lt;/a&gt;: um projeto que tem crescido muito e traz consigo o objetivo de ser uma alternativa para o front-end do YouTube, reduzindo a quantidade de JavaScript a ser renderizado, sendo distribuído sob licença AGPL 3.0&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/jeromegn/slang"&gt;Slang&lt;/a&gt;: uma linguagem de template inspirada no Slim para construção de páginas web sem a necessidade de escrever HTML puro&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/lucaong/immutable"&gt;Immutable&lt;/a&gt;: uma implementação completa de uma coleção de estruturas de dados imutáveis, thread-safe e persistente, possibilitando uma melhor segurança no processamento e acesso de memória&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/vitobotta/hetzner-k3s"&gt;Hetzner k3s&lt;/a&gt;: um CLI completo para gerenciamento de cluster Kubernetes na Hetzner Cloud utilizando a distribuição k3s&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/lanjoni/hackacrow"&gt;Hackacrow&lt;/a&gt;: um CLI completo para validação de entradas e saídas de comandos, sendo útil principalmente para validações de respostas em hackathons&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/veelenga/crystal-zsh"&gt;Crystal ZSH&lt;/a&gt;: um plugin completo para utilizar junto do &lt;code&gt;oh-my-zsh&lt;/code&gt;, permitindo uma integração magnífica com seu terminal durante o desenvolvimento com Crystal&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/veelenga/awesome-crystal#editor-plugins"&gt;Editor Plugins&lt;/a&gt;: sendo uma área específica de outro repositório (&lt;code&gt;awesome-crystal&lt;/code&gt; nesse caso) conta com plugins interessantes para diferentes editores de código, por isso, recomendo conhecer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Existem diversos outros projetos com Crystal, porém, acredito que vale a pena conferir o primeiro repositório citado anteriormente para conhecer outros projetos que utilizam Crystal. &lt;/p&gt;

&lt;h2&gt;
  
  
  Finalização
&lt;/h2&gt;

&lt;p&gt;O intuito desse artigo foi apresentar um pouco mais sobre a proposta que Crystal traz no mundo de desenvolvimento de software, citando ferramentas e projetos que podem fazer parte do seu cotidiano de desenvolvimento.&lt;/p&gt;

&lt;p&gt;Se você quer aprender um pouco mais sobre Crystal com um conteúdo totalmente gratuito, em português, escrito com muito amor e carinho, recomendo a leitura do &lt;a href="https://github.com/lanjoni/crystal4noobs"&gt;crystal4noobs&lt;/a&gt;, um conteúdo parte do projeto &lt;a href="https://github.com/he4rt/4noobs"&gt;4noobs&lt;/a&gt; da &lt;a href="https://heartdevs.com/"&gt;He4rt Devs&lt;/a&gt;, trazendo conteúdo de qualidade para todos de forma gratuita.&lt;/p&gt;

&lt;p&gt;Agradeço imensamente se você chegou até aqui e espero que tenha gostado da leitura. Não se esqueça de deixar sua estrelinha os repositórios apresentados. Espero que esse artigo possa &lt;em&gt;crystalizar&lt;/em&gt; ainda mais sua rotina de desenvolvimento. Meu muito obrigado e até a próxima!&lt;/p&gt;

&lt;p&gt;Da comunidade para a comunidade 💜💛&lt;/p&gt;

</description>
      <category>crystal</category>
      <category>ruby</category>
      <category>braziliandevs</category>
      <category>programming</category>
    </item>
    <item>
      <title>Criando Exceptions para impressionar no Teste Técnico</title>
      <dc:creator>Daniel Reis</dc:creator>
      <pubDate>Tue, 10 Oct 2023 01:03:22 +0000</pubDate>
      <link>https://dev.to/he4rt/criando-exceptions-para-impressionar-no-teste-tecnico-2nie</link>
      <guid>https://dev.to/he4rt/criando-exceptions-para-impressionar-no-teste-tecnico-2nie</guid>
      <description>&lt;p&gt;Exceptions sempre vai ser um assunto constante quando o tópico for &lt;strong&gt;Orientação à Objetos&lt;/strong&gt; e hoje vamos descobrir como criá-las como um artesão de software!&lt;/p&gt;

&lt;h2&gt;
  
  
  Tabela de Conteúdo
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;1. Prólogo&lt;/li&gt;
&lt;li&gt;2. O que gostariamos de EVITAR&lt;/li&gt;
&lt;li&gt;3. Refatoração 1: Criando Exceptions&lt;/li&gt;
&lt;li&gt;4. Design Patterns: Factory Pattern&lt;/li&gt;
&lt;li&gt;5. Refatoração 2: Refinando as Exceptions&lt;/li&gt;
&lt;li&gt;6. Conclusão&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  1. Prólogo
&lt;/h2&gt;

&lt;p&gt;Quando eu comecei a estudar programação, um dos assuntos que sempre me assustava eram "erros" ou qualquer coisa que se relaciona à isso, porém após começar a estudar com mais frequencia, eu entendi que os erros e/ou &lt;strong&gt;Exceptions&lt;/strong&gt; são muito mais amigas do que inimigas. Mas é claro que pra isso, você precisa entender como utilizar de um jeito interessante pro seu projeto.&lt;/p&gt;

&lt;p&gt;No meu caso, acabava usando o trecho &lt;code&gt;throw new Exception()&lt;/code&gt; pra literalmente qualquer coisa e me perdia facilmente na codebase, por conta de uma Exception genérica espalhada no meio de tantas outras. No início não tinha problema, eu ainda não trabalhava com time que tinha observabilidade, então tá tudo certo.&lt;/p&gt;

&lt;p&gt;Passado o tempo, entrei em mais empresas FODAS e me deparei com excelentes implementações de Exceptions, principalmente o &lt;code&gt;Factory Pattern&lt;/code&gt;. Esse método me deixou maravilhado em como as coisas podem ser &lt;code&gt;simples e elegantes&lt;/code&gt;, mesmo quando se trata de erros.&lt;/p&gt;

&lt;p&gt;E hoje vou mostrar pra vocês como criar um certo &lt;strong&gt;gosto&lt;/strong&gt; em escrever exceções elegantes pra não encher seu código com 2km de mensagem de erro dentro da regra de negócio. &lt;/p&gt;

&lt;h2&gt;
  
  
  2. O que gostariamos de EVITAR
&lt;/h2&gt;

&lt;p&gt;Vamos começar dando um pouco de contexto para esse tutorial: imagine que você tá desenvolvendo um sistema de RPG e nele você precisa criar um inventário simples pro seu personagem.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src
├── Item
│   └── Item.php
└── Player
    ├── Inventory.php
    └── Player.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dentro desse contexto, imagine que você está tentando equipar um item no seu personagem. Porém, é lógico que nós vamos colocar algumas regras de validação com suas devidas &lt;code&gt;Exceptions&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;DanielHe4rt\Player&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;DanielHe4rt\Item\Item&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Player&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$level&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="kt"&gt;Inventory&lt;/span&gt; &lt;span class="nv"&gt;$inventory&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;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;equipItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Item&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;hasItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;\Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s1"&gt;'Você não possui o item "'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'". '&lt;/span&gt;
            &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;minLevel&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;\Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s1"&gt;'Você não pode equipar o item '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'pois o nível minimo é '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;minLevel&lt;/span&gt; 
            &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setupItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;


    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;setupItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Item&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; 
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// faça coisas iradas&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;Vimos que existem duas regras de validação que jogam exceções diferentes pro nosso cliente. E pasmem: isso funciona (num cenário que o código tá completinho) e cumpre o papel de validação... MASSSSS, depois que eu aprendi que em testes de emprego o que é visto é a &lt;strong&gt;QUALIDADE DA ENTREGA&lt;/strong&gt; e não a agilidade que foi criado, meu mundo deu uma leve mudada para entender como transformar coisas que parecem "estranhas e feias" em coisas "simples e elegantes".&lt;/p&gt;

&lt;p&gt;Nesse caso, eu gostaria muito de evitar duas coisas: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Exceptions genéricas;&lt;/li&gt;
&lt;li&gt;Exceptions que tomam conta de locais para regra de negócio.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Não entenda errado, as exceptions vão continuar onde elas estão, porém vamos melhorar a legibilidade do código.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Refatoração 1 : Criando Exceptions
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src
├── Item
│   └── Item.php
└── Player
    ├── Exceptions
    │   ├── PlayerException.php
    │   └── PlayerInventoryException.php
    ├── Inventory.php
    └── Player.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Beleza, agora passamos para parte que criamos nossa primeira &lt;code&gt;Exception&lt;/code&gt; "customizada", onde só estendemos a Exception base para uma nova classe. Não é nada de outro mundo, mas já melhora nossa legibilidade e entendimento do código em alguns vários pontos.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;DanielHe4rt\Player&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PlayerException&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;\Exception&lt;/span&gt;
&lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PlayerInventoryException&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;\Exception&lt;/span&gt;
&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E faremos uma refatoração simples na nossa função &lt;code&gt;equipItem()&lt;/code&gt;, reposicionando as exceptions padrões pela nova exception que criamos.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;
&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;DanielHe4rt\Player&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;DanielHe4rt\Item\Item&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;DanielHe4rt\Player\PlayerException&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;DanielHe4rt\Player\PlayerInventoryException&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Player&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$level&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="kt"&gt;Inventory&lt;/span&gt; &lt;span class="nv"&gt;$inventory&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;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;equipItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Item&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;hasItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PlayerInventoryException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s1"&gt;'Você não possui o item "'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'". '&lt;/span&gt;
            &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;minLevel&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PlayerException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s1"&gt;'Você não pode equipar o item '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'pois o nível minimo é '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;minLevel&lt;/span&gt; 
            &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setupItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;


    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;setupItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Item&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; 
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// faça coisas iradas&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;Com as novas Exceptions, agora sabemos exatamente do que se trata e principalmente onde buscar na nossa codebase quando essa Exception estourar.  É literalmente um &lt;code&gt;CTRL + ALT + F&lt;/code&gt; e pesquisar o nome &lt;strong&gt;"PlayerInventoryException"&lt;/strong&gt;. Facilita sua vida, a vida do DevOps que vai meter isso num NewRelic/DataDog da vida e assim seguimos. &lt;/p&gt;

&lt;p&gt;Porém algo ainda me incomoda muito... Por quê essas mensagens gigantes estão no meio da regra de negócio? Misturar pt-br com en desse jeito é triste d+ pra mim desculpa amigos!! Vamos aprender um jeito de por isso debaixo dos panos, porém antes precisamos passar num tópico de Design Pattern chamado Factory!&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Design Patterns: Factory Pattern
&lt;/h2&gt;

&lt;p&gt;Se você já ouviu falar de &lt;strong&gt;Design Patterns&lt;/strong&gt;, provavelmente já entende um pouco sobre o que isso resolve. Mas caso não, eu te explico!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Design Patterns são soluções genéricas para problemas genéricos." - Alguém por ai&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Nessa ideia de problemas genéricos, uma galera se reuniu e começou a criar alguns principios de Design de Software pra você resolver problemas do dia a dia com uma certa agilidade. Os Design Patterns são divididos em três tipos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Padrões Comportamentais;&lt;/li&gt;
&lt;li&gt;Padrões Criacionais;&lt;/li&gt;
&lt;li&gt;Padrões Estruturais.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;e você pode ler mais sobre eles no site &lt;a href="//https:/refactoring.guru"&gt;https:/refactoring.guru&lt;/a&gt; e eu recomendo MUITO pra qualquer pessoa desenvolvedora explorar essa documentação e se auto desenvolver. Ok, mas vamos focar nele, o tal do &lt;a href="https://refactoring.guru/pt-br/design-patterns/factory-method" rel="noopener noreferrer"&gt;Criacional de Fábrica (ou Factory Method)&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;A ideia desse padrão é você criar objetos sem ter que instaciar mil coisas em classes diferentes, você literalmente fabricar alguma instância de algo e só receber numa chamada simples de alguma função. No mundo da programação existem centenas de milhões de chamadas como &lt;code&gt;Models::make()&lt;/code&gt;, &lt;code&gt;Exception::create()&lt;/code&gt;, &lt;code&gt;ApiQualquer::factory()&lt;/code&gt; pra você não ter que acessar o método construtor de uma respectiva classe.&lt;/p&gt;

&lt;p&gt;Dando o exemplo de um Client de API, onde deixamos o construtor modular pra caso precisemos trocar a chave e segredo PORÉM ainda damos a possibilidade de uma chamada rápida fabricando o objeto final:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GithubClient&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$clientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$clientSecret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'clientId'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$clientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'clientSecret'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$clientSecret&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="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;make&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;self&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'github.client_id'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'github.client_secret'&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="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getUserByUsername&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'danielhe4rt'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; 
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// faça uma chamada pro github..&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Chamando sem fabricar o objeto&lt;/span&gt;

&lt;span class="nv"&gt;$client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GithubClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'client-id-foda'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'client-secret-foda'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getUserByUsername&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'danielhe4rt'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Chamando usando o Factory &lt;/span&gt;

&lt;span class="nv"&gt;$client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GithubClient&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getUserByUsername&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'danielhe4rt'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Nós fizemos uma chamada estática fabricando todos os parâmetros de um jeito sucinto. Esse "make/factory" ou o que você quiser chamar, pode ser um método bem extenso dependendo do que você for injetar mas isso não chega a ser problema.&lt;/p&gt;

&lt;p&gt;Mas de qualquer forma, vimos que a legibilidade usando o Factory Pattern foi melhorada, claro que você pode colocar melhores nomes pras funções mas na base é isso. Agora voltemos para nossas exceptions!&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Refatoração 2: Refinando as Exceptions
&lt;/h2&gt;

&lt;p&gt;Show, aprendemos um pouquinho sobre o factory, agora vamos aplicar. &lt;/p&gt;

&lt;p&gt;Criaremos um método de factory para nossa exception que faça sentido com o contexto do que tá acontecendo. Pois é, nada de usar "make" ou "create" nesses momentos. Exception precisam contar minimamente uma história pro usuário ou pro desenvolvedor do quê tá acontecendo e vamos focar nisso.&lt;/p&gt;

&lt;p&gt;Depois de uma leve refatoração na nossa &lt;code&gt;PlayerInventoryException&lt;/code&gt;, temos o resultado de:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PlayerInventoryException&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;\Exception&lt;/span&gt; 
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;itemNotFound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$itemName&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;self&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Você não possui o item "%s".'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$itemName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;403&lt;/span&gt; &lt;span class="c1"&gt;// Forbidden&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E após chamarmos essa factory em nosso código, podemos perceber uma melhora de leitura e mantenibilidade já que isolamos as informações da Exception dentro da mesma.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;equipItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Item&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;hasItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;PlayerInventoryException&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;itemNotFound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;minLevel&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PlayerException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;'Você não pode equipar o item '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'pois o nível minimo é '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;minLevel&lt;/span&gt; 
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setupItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&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;Agora refatorando a próxima, temos a mesma ideia de trocar a PlayerException.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PlayerException&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;\Exception&lt;/span&gt; 
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;lowLevelForThisEquipment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$itemName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$itemLevel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;self&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="nv"&gt;$message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;'Você não pode equipar o item %s pois o nível minimo é %s.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nv"&gt;$itemName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nv"&gt;$itemLevel&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;403&lt;/span&gt; &lt;span class="c1"&gt;// Forbidden&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;e agora nossa &lt;code&gt;equipItem()&lt;/code&gt; tá ó, uma maravilha!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;equipItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Item&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;hasItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;PlayerInventoryException&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;itemNotFound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;minLevel&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;PlayerException&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;lowLevelForThisEquipment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;minLevel&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setupItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&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;Tá uma maravilha? Tá. Mas ainda tem algo me incomodando... Por quê passar os tipos primitivos sendo que essas exceptions estão se "comunicando" com classes?&lt;/p&gt;

&lt;p&gt;Ficaria bem mais limpo se passarmos a referência do objeto inteiro pra Exception e lá dentro ela resolve o que precisa usar. Afinal, vai que precisamos de mais algo num futuro e não custa nada já deixar bonitinho, né?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;DanielHe4rt\Player&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;DanielHe4rt\Item\Item&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PlayerException&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;\Exception&lt;/span&gt; 
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;lowLevelForThisEquipment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Item&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;self&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="nv"&gt;$message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;'Você não pode equipar o item %s pois o nível minimo é %s.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;minLevel&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;403&lt;/span&gt; &lt;span class="c1"&gt;// Forbidden&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PlayerInventoryException&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;\Exception&lt;/span&gt; 
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;itemNotFound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Item&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;self&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="nv"&gt;$message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Você não possui o item "%s".'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;403&lt;/span&gt; &lt;span class="c1"&gt;// Forbidden&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;E o resultado final do nosso método fica só o charme, tendo exceptions encapsuladas e ainda vai te gerar ótimos feedbacks na sua entrevista de emprego.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;DanielHe4rt\Player&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;DanielHe4rt\Item\Item&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;DanielHe4rt\Player\PlayerException&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;DanielHe4rt\Player\PlayerInventoryException&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Player&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$level&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="kt"&gt;Inventory&lt;/span&gt; &lt;span class="nv"&gt;$inventory&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;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;equipItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Item&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;hasItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;PlayerInventoryException&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;itemNotFound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;minLevel&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;PlayerException&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;lowLevelForThisEquipment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setupItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;


    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;setupItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Item&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; 
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// faça coisas iradas&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;
  
  
  6. Conclusão
&lt;/h2&gt;

&lt;p&gt;Exceptions são de longe uma das coisas mais "chatas" de se lidar. Afinal ninguém quer erro estourando na tela do cliente, mas no geral elas só precisam ter uma boa escrita e adicionar um pouquinho de charme com chamadas estáticas e &lt;strong&gt;PLAU&lt;/strong&gt; tu ganha um elogio e ponto positivo na entrevista de emprego.&lt;/p&gt;

&lt;p&gt;Espero que vocês tenham curtido o conteúdo e não esqueça de me seguir nas redes sociais!&lt;/p&gt;

&lt;p&gt;Referência: &lt;a href="https://www.rosstuck.com/formatting-exception-messages" rel="noopener noreferrer"&gt;Formatting Exception Messages&lt;/a&gt;&lt;/p&gt;

</description>
      <category>oop</category>
      <category>php</category>
      <category>braziliandevs</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Breaking Terraform files into composable layers</title>
      <dc:creator>Lucas F. da Costa</dc:creator>
      <pubDate>Thu, 14 Sep 2023 23:20:10 +0000</pubDate>
      <link>https://dev.to/he4rt/breaking-terraform-files-into-composable-layers-1mj8</link>
      <guid>https://dev.to/he4rt/breaking-terraform-files-into-composable-layers-1mj8</guid>
      <description>&lt;p&gt;Terraform allows you to spin up cloud infrastructure using a single command. Let's say you're trying to run Elasticsearch and Kibana within a Kubernetes cluster, for example.&lt;/p&gt;

&lt;p&gt;For that, you could write a few &lt;code&gt;.tf&lt;/code&gt; files and run &lt;code&gt;terraform apply&lt;/code&gt; to provision a Kubernetes cluster and deploy a few pods to it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fgcuyfxzx1putejr1rl8t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fgcuyfxzx1putejr1rl8t.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, assume you want other instances of the Elastic stack that you can use for demos. In that case, you'll have to set up brand new &lt;a href="https://developer.hashicorp.com/terraform/language/state/workspaces" rel="noopener noreferrer"&gt;Terraform workspaces&lt;/a&gt; and run &lt;code&gt;terraform apply&lt;/code&gt; multiple times.&lt;/p&gt;

&lt;p&gt;The problem with this approach is that it will cause you to replicate your &lt;em&gt;entire&lt;/em&gt; infrastructure every time. Consequently, you'll have multiple Kubernetes clusters. Each cluster takes at least 15 minutes to spin up and costs $72 a month on AWS.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fhxcjdv5zbm87e5hqrdpf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fhxcjdv5zbm87e5hqrdpf.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A much better alternative would be to reuse a single Kubernetes cluster and spin up multiple environments on top of it. Thus, you'd pay for a single cluster, and you don't have to wait for a brand new cluster every time you spawn a new environment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fdm7mo4o6ub7y1w0mw6xj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fdm7mo4o6ub7y1w0mw6xj.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's why &lt;a href="https://github.com/ergomake/layerform" rel="noopener noreferrer"&gt;Layerform&lt;/a&gt; exists. Layerform allows engineers to break their Terraform files into composable &lt;em&gt;layers&lt;/em&gt;. That way, teams can have a shared base layer for their Kubernetes cluster and multiple top layers with Elasticsearch, Kibana, and even other serverless components they need, like Lambdas, SQS queues, or load balancers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fap5kps52rrkjqw09hlj5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fap5kps52rrkjqw09hlj5.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this post, I'll explain how layers work, how to break your Terraform files into composable layers and demonstrate a few use cases that layers enable. Those use cases include creating production-like development environments and setting up pull request preview links for &lt;em&gt;any&lt;/em&gt; type of application.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;As a note, Terraform workspaces are quite laborious to use. The official documentation even contains a warning about using them.&lt;br&gt;
&lt;em&gt;&lt;strong&gt;Important&lt;/strong&gt;: Workspaces are not appropriate for system decomposition or deployments requiring separate credentials and access controls.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;



&lt;h2&gt;
  
  
  How layers work
&lt;/h2&gt;

&lt;p&gt;In Layerform, Terraform files are divided into layers. Each layer definition specifies the files that belong to it and a list of dependencies.&lt;/p&gt;

&lt;p&gt;Let's say you're trying to run Elasticsearch and Kibana within an EKS instance (AWS-managed k8s).&lt;/p&gt;

&lt;p&gt;For that, you'd have two layers, one for &lt;code&gt;eks&lt;/code&gt;, and another for the &lt;code&gt;elastic_stack&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"layers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"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;"eks"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"files"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"layers/eks.tf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"layers/eks/**"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"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;"elastic_stack"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"files"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"layers/elastic_stack.tf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"layers/elastic_stack/**"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"eks"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the &lt;code&gt;eks&lt;/code&gt; layer definition, you specify which files belong to that layer. Given that &lt;code&gt;eks&lt;/code&gt; does &lt;em&gt;not&lt;/em&gt; depend on any other infrastructure, it won't list any other layers as dependencies. On the other hand, the &lt;code&gt;elastic_stack&lt;/code&gt; layer's files depend on &lt;code&gt;eks&lt;/code&gt;. Consequently, it will include &lt;code&gt;eks&lt;/code&gt; as a dependency.&lt;/p&gt;

&lt;p&gt;After defining layers this way, you can spin up an EKS cluster independently by running &lt;code&gt;layerform spawn eks default&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fypdqh69w89w46ykwhwqq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fypdqh69w89w46ykwhwqq.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, each engineer can create their own instances of the &lt;code&gt;elastic_stack&lt;/code&gt; and reuse the same cluster. For that, they'd all run &lt;code&gt;layerform spawn elastic_stack &amp;lt;name&amp;gt;&lt;/code&gt;, and each of these layers would look for an underlying &lt;code&gt;eks&lt;/code&gt; layer with ID &lt;code&gt;default&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2F9s8v5628ww5himz8irzy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2F9s8v5628ww5himz8irzy.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜ layerform list instances
INSTANCE NAME                LAYER NAME    DEPENDENCIES
default                      eks
first                        elastic_stack  eks=default
second                       elastic_stack  eks=default
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If someone needs a new cluster, they can recreate the whole stack by passing the &lt;code&gt;--base&lt;/code&gt; flag, as in &lt;code&gt;layerform spawn elastic_stack third --base eks=another_eks&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2F7gvyssbz2m54d2xl01d7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2F7gvyssbz2m54d2xl01d7.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜ layerform list instances
INSTANCE NAME                LAYER NAME    DEPENDENCIES
default                      eks
another_eks                  eks
first                        elastic_stack  eks=default
second                       elastic_stack  eks=default
third                        elastic_stack  eks=another_eks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When done with their layers, engineers can kill their layer instances without damaging someone else's work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fpwd33a7q2x1hk0uaaiwl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fpwd33a7q2x1hk0uaaiwl.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In a way, Layerform's layers are similar to container layers. Some people refer to them as "Kustomize for Terraform."&lt;/p&gt;



&lt;h2&gt;
  
  
  Using layers to create development environments
&lt;/h2&gt;

&lt;p&gt;For most teams, "staging" is a bottleneck. Usually, companies have many engineers, but there's only a single "staging" environment for everyone to use. Consequently, developers wanting to test changes in a production-like environment must queue.&lt;/p&gt;

&lt;p&gt;Additionally, when systems are large or depend on serverless components, engineers cannot reliably run all their software on their machines. Therefore, they all point to the same staging environment and step on each others' toes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fdn62yxb4ac16mziqizlk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fdn62yxb4ac16mziqizlk.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Layers help teams solve this problem by enabling each engineer to spin up their own environment and share core pieces of infrastructure, as shown earlier.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fvr8ktr4zhtfrxblrl2ty.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fvr8ktr4zhtfrxblrl2ty.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Layerform also provides developers valuable features to develop applications locally while pointing them to their own "staging" environment's back-end.&lt;/p&gt;

&lt;p&gt;Assume you cannot run Elasticsearch locally because the JVM needs too much memory, for example. In that case, you could use &lt;code&gt;layerform output&lt;/code&gt; to get the address of your remote Elasticsearch instance.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜ layerform output elastic_stack first

{
  "cluster_name": {
    "sensitive": false,
    "type": "string",
    "value": "demo-eks-post-example"
  },
  "elasticsearch_url": {
    "sensitive": false,
    "type": "string",
    "value": "https://elasticsearch-post-example.environment.ergomake.link"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You could use that URL in your Kibana configuration file (&lt;code&gt;config.yml&lt;/code&gt;). That way, you could point your local Kibana to the remote Elasticsearch as you develop.&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;elasticsearch.hosts&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;https://elasticsearch-post-example.environment.ergomake.link"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In case you had multiple outputs you need to interpolate in your configuration file, as many applications have, you could even write a template file and tell Layerform to interpolate it for you.&lt;/p&gt;

&lt;p&gt;Take the following template file for Kibana's &lt;code&gt;config.yml&lt;/code&gt;, for example.&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;elasticsearch.hosts&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;{{elasticsearch_url.value}}"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To render that file and replace &lt;code&gt;{{ elasticsearch_url.value }}&lt;/code&gt; with the actual values for your particular layer, you could use the &lt;code&gt;--template&lt;/code&gt; flag to output the rendered file to &lt;code&gt;stdout&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="s"&gt;➜ layerform output elastic_stack first --template config/kibana-template.yml&lt;/span&gt;

&lt;span class="na"&gt;elasticsearch.hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;http://elastic-stack-xehl-es.environment.ergomake.link'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Engineers could then pipe the rendered file to an actual &lt;code&gt;config.yml&lt;/code&gt; and use it without knowing anything about layers or manually entering values.&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="s"&gt;➜ layerform output elastic_stack first --template config/kibana-template.yml &amp;gt; config.yml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, &lt;code&gt;yarn start&lt;/code&gt; would cause Kibana to read &lt;code&gt;config.yml&lt;/code&gt; and point to the remote Elasticsearch.&lt;/p&gt;



&lt;h3&gt;
  
  
  A note on collaborating with layers
&lt;/h3&gt;

&lt;p&gt;Similarly to Terraform, Layerform may use a remote back-end to store states and layer definitions.&lt;/p&gt;

&lt;p&gt;Once an engineer creates a layer instance, Layerform syncs it with a remote state file in S3. Then, when someone else uses &lt;code&gt;layerform list instances&lt;/code&gt;, Layerform will fetch states from the remote back-end and list an up-to-date list of instances.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2F38dvovqdoplf7l9z1j4z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2F38dvovqdoplf7l9z1j4z.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Assume engineer A spun up layer &lt;code&gt;first&lt;/code&gt;. If engineer B wants to collaborate with engineer A, they will be able to see &lt;code&gt;first&lt;/code&gt; in the list of layers because the list is stored remotely.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Ran by engineer B
➜ layerform list instances

INSTANCE NAME                LAYER NAME     DEPENDENCIES
default                      eks
first                        elastic_stack  eks=default
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, engineer B can also get the URL of the Elasticsearch instance in layer &lt;code&gt;first&lt;/code&gt; by running &lt;code&gt;layerform output&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We're still working on proper locking and collaboration mechanisms to keep states free of race conditions. We welcome pull requests for that in case you're interested in contributing.&lt;/p&gt;
&lt;/blockquote&gt;



&lt;h2&gt;
  
  
  Using layers to create pull request previews
&lt;/h2&gt;

&lt;p&gt;Whenever engineers make changes and open a pull request, Vercel spins up their Next.js application and adds a preview link to the PR.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2F1ssnxqyr3p3pxlah6inw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2F1ssnxqyr3p3pxlah6inw.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most developers, designers, and product managers love Vercel's previews. &lt;strong&gt;The problem is that these previews do not work for teams with more complex set-ups&lt;/strong&gt;. For example, you can't get full-stack previews if you have a serverless application or need a Kubernetes cluster.&lt;/p&gt;

&lt;p&gt;Today, most teams end up building these preview environments in-house, using a bunch of ad-hoc scripts.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/ergomake/layerform" rel="noopener noreferrer"&gt;Layerform&lt;/a&gt; helps teams solve this problem by allowing them to create a separate preview layer. Then, engineers can call the Layerform CLI from their CI and spin up only the infrastructure they need.&lt;/p&gt;

&lt;p&gt;Since Layerform uses Terraform files, it can spin up all types of infrastructure for these previews, including Kubernetes resources, Lambdas, SQS instances, and S3 buckets.&lt;/p&gt;

&lt;p&gt;In the case of the Elastic stack, for example, engineers could create a GitHub action to &lt;code&gt;layerform spawn&lt;/code&gt; only the Elastic stack layer on top of a shared &lt;code&gt;preview&lt;/code&gt; cluster, making previews much less expensive.&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Preview&lt;/span&gt;

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

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;preview&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

        &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# + Checkout code, install deps, etc...&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;Install Layerform&lt;/span&gt;
              &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;go install github.com/ergomake/layerform@main&lt;/span&gt;

            &lt;span class="c1"&gt;# Configure remote state — we'll improve this&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;Create Layerform config file&lt;/span&gt;
              &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
                  &lt;span class="s"&gt;mkdir -p ~/.layerform&lt;/span&gt;
                  &lt;span class="s"&gt;echo "currentContext: demo" &amp;gt; ~/.layerform/config&lt;/span&gt;
                  &lt;span class="s"&gt;echo "contexts:" &amp;gt;&amp;gt; ~/.layerform/config&lt;/span&gt;
                  &lt;span class="s"&gt;echo "  demo:" &amp;gt;&amp;gt; ~/.layerform/config&lt;/span&gt;
                  &lt;span class="s"&gt;echo "    type: s3" &amp;gt;&amp;gt; ~/.layerform/config&lt;/span&gt;
                  &lt;span class="s"&gt;echo "    bucket: layerform-post-demo" &amp;gt;&amp;gt; ~/.layerform/config&lt;/span&gt;
                  &lt;span class="s"&gt;echo "    region: us-west-1" &amp;gt;&amp;gt; ~/.layerform/config&lt;/span&gt;

            &lt;span class="c1"&gt;# Actually spawn the preview layer&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;layerform&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;Spawn Elasticstack using Layerform&lt;/span&gt;
              &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
                  &lt;span class="s"&gt;layerform spawn elastic_stack ${{ github.event.pull_request.head.ref }}&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;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_ACCESS_KEY_ID }}&lt;/span&gt;
                  &lt;span class="na"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_SECRET_ACCESS_KEY }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The workflow above will use the pull request's branch name as the layer instance's name.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Just like any other layer instance, it will appear when running &lt;code&gt;layerform list&lt;/code&gt;, so you can connect to it from your terminal too, if you want.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Finally, once the &lt;code&gt;spawn&lt;/code&gt; is done, you can write some code to call &lt;code&gt;layerform output&lt;/code&gt; and add a comment with a preview link to the pull request.&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;# ...&lt;/span&gt;

&lt;span class="c1"&gt;# Get Kibana's URL from layerform output using jq&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;layerform&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;Spawn Elasticstack using Layerform&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;layerform spawn elastic_stack ${{ github.event.pull_request.head.ref }}&lt;/span&gt;
      &lt;span class="s"&gt;kibana=$(layerform output elastic_stack ${{ github.event.pull_request.head.ref }} | jq -r .kibana_url.value)&lt;/span&gt;
      &lt;span class="s"&gt;echo "kibana=$kibana" &amp;gt;&amp;gt; "$GITHUB_OUTPUT"&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;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_ACCESS_KEY_ID }}&lt;/span&gt;
      &lt;span class="na"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_SECRET_ACCESS_KEY }}&lt;/span&gt;

&lt;span class="c1"&gt;# Add a comment to the pull request&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/github-script@v6&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;github.rest.issues.createComment({&lt;/span&gt;
            &lt;span class="s"&gt;issue_number: context.issue.number,&lt;/span&gt;
            &lt;span class="s"&gt;owner: context.repo.owner,&lt;/span&gt;
            &lt;span class="s"&gt;repo: context.repo.repo,&lt;/span&gt;
            &lt;span class="s"&gt;body: '${{ steps.layerform.outputs.kibana }}'&lt;/span&gt;
          &lt;span class="s"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that change, you'll see a preview link pointing to actual production-like infrastructure in every PR you open.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Ftfarsc0kamv6bumhubfx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Ftfarsc0kamv6bumhubfx.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To destroy these layer instances, you should create a second workflow that's triggered when someone closes or merges a PR. That workflow should call &lt;code&gt;layerform kill&lt;/code&gt; and pass it the layer's name, which, in this case, is the same as the branch's.&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Destroy previews when PRs are closed&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;closed&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;kill_preview&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
        &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# + Checkout code, install deps, etc...&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;Install Layerform&lt;/span&gt;
              &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;go install github.com/ergomake/layerform@main&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;Kill preview&lt;/span&gt;
              &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;layerform kill elastic_stack ${{ github.event.pull_request.head.ref }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Extra implementation notes
&lt;/h2&gt;

&lt;p&gt;In these examples, I assume that users have deployed an &lt;a href="https://docs.nginx.com/nginx-ingress-controller/" rel="noopener noreferrer"&gt;&lt;code&gt;nginx-ingress-controller&lt;/code&gt;&lt;/a&gt; to their cluster through the &lt;code&gt;eks&lt;/code&gt; layer. This controller is responsible for creating an &lt;code&gt;nlb&lt;/code&gt; and exposing Elasticsearch and Kibana to the internet through their ingresses.&lt;/p&gt;

&lt;p&gt;In any case, many developers would probably want to keep their development environments within a VPN, which is also possible because Layerform provisions all the infrastructure in &lt;em&gt;your&lt;/em&gt; cloud using plain Terraform files.&lt;/p&gt;

&lt;p&gt;When it comes to deploying the actual pods to your cluster, you can use whatever you want. When I tested the examples myself, I specified raw Kubernetes resources through the &lt;code&gt;kubernetes&lt;/code&gt; provider. Still, you could use Helm charts with the Helm provider. The only requirement for using layers is that your infra is in a Terraform file, not ad-hoc scripts.&lt;/p&gt;

&lt;p&gt;Finally, another interesting detail is that engineers must provide layer definitions to the remote back-end with &lt;code&gt;layerform configure&lt;/code&gt; before anyone can spawn instances.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you've found this idea intriguing, please consider &lt;a href="https://github.com/ergomake/layerform" rel="noopener noreferrer"&gt;giving us a star on GitHub&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
