<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Danilo Lima on Medium]]></title>
        <description><![CDATA[Stories by Danilo Lima on Medium]]></description>
        <link>https://medium.com/@danlima-dev?source=rss-c263b528da4e------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*kTAc4hXqajJ-Vr8S5jNQAQ.jpeg</url>
            <title>Stories by Danilo Lima on Medium</title>
            <link>https://medium.com/@danlima-dev?source=rss-c263b528da4e------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Tue, 16 Jun 2026 23:01:44 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@danlima-dev/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Generics: Invariância, Covariância e Contravariâcia]]></title>
            <link>https://danlima-dev.medium.com/generics-invari%C3%A2ncia-covari%C3%A2ncia-e-contravari%C3%A2cia-a803e2aeb486?source=rss-c263b528da4e------2</link>
            <guid isPermaLink="false">https://medium.com/p/a803e2aeb486</guid>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[oop]]></category>
            <category><![CDATA[generics]]></category>
            <dc:creator><![CDATA[Danilo Lima]]></dc:creator>
            <pubDate>Sun, 19 Oct 2025 18:36:37 GMT</pubDate>
            <atom:updated>2025-10-19T18:40:01.603Z</atom:updated>
            <content:encoded><![CDATA[<p>Se você estiver familiarizado com linguagens estaticamente tipadas, é muito provável que já tenha sido apresentado ao conceito de <em>Generics.</em> Que permite flexibilizar a tipagem do código, deixando-a mais “genérica”, ou seja, menos rígida.</p><p>Nesse post vamos aprofundar o conhecimento em <em>Generics</em>, mais especificamente nesses três conceitos: <strong>Invariância</strong>, <strong>Covariância </strong>e <strong>Contravariância</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eEs0zGzk8QNOGLaY2HxFJg.png" /></figure><p>Antes de começar, vamos recordar do uso de Generics</p><pre>class Box&lt;T&gt;(val content: T) {<br>    val list = mutableListOf&lt;T&gt;()<br><br>    fun store() {<br>        list.add(content)<br>    }<br>}</pre><p><strong>T </strong>aqui não é um tipo especifico, que exista no Kotlin, mas um <em>alias, </em>que diz<em>: olha, esse T pode ser qualquer tipo declarado quando a classe for instanciada.</em></p><p>Num cenário onde <em>Generics</em> não existisse, teríamos que contornar o suporte criando múltiplas declarações similar a acima para cada tipo que precisássemos.</p><h3>Aprofundando</h3><p>Agora que revisamos o conhecimento sobre Generics, vamos ao foco desse post: <strong>Invariância</strong>, <strong>Covariância </strong>e <strong>Contravariância</strong>.</p><p>Esses três conceitos são características de um tipo <em>Genérico</em>, que define o comportamento dele em relação à hierarquia de tipos.</p><h4>Invariância</h4><p>Dizemos que um tipo genérico &lt;T&gt; é invariante quando nenhuma substituição de tipo é permitida, garantindo que o tipo seja o mesmo na leitura e na escrita:</p><pre>open class Food(val name: String)<br>class Vegetable(name: String) : Food(name)<br><br>class Box&lt;T&gt;(val content: T) {<br>    val list = mutableListOf&lt;T&gt;()<br><br>    fun store() {<br>        list.add(content)<br>    }<br>}<br><br>val foodBox: Box&lt;Food&gt; = Box(Food(&quot;beans&quot;)) // funciona<br>val vegBox: Box&lt;Vegetable&gt; = Box(Vegetable(&quot;carrot&quot;)) // funciona<br><br>val invalidBox: Box&lt;Food&gt; = vegBox // Não funciona</pre><p>No exemplo não funcional o tipo Vegetablenão pode ser atribuído à variável invalidBox,<strong> </strong>pois espera-se exatamente o que foi definido um tipo Box&lt;Food&gt;<strong>.</strong></p><p>Isso é um comportamento que chamamos de invariante em um tipo genérico.</p><h4>Covariância</h4><p>Agora, um tipo genérico &lt;T&gt; é covariante quando a substituição por subtipos é permitida, mas apenas na leitura, confuso? Vamos ao exemplo pra ficar mais claro:</p><pre>open class Food(val name: String)<br>class Vegetable(name: String) : Food(name)<br><br>class Box&lt;out T&gt;(val content: T) {<br>    fun first(): T = content<br>    fun print(msg: T) { // 1. Não funciona, T não pode ser usado na entrada<br>        println(msg)<br>    }<br>}<br><br>val foodBox: Box&lt;Food&gt; = Box(Food(&quot;beans&quot;)) // funciona<br>val vegBox: Box&lt;Vegetable&gt; = Box(Vegetable(&quot;carrot&quot;)) // funciona<br>val vegLeafBox: Box&lt;Food&gt; = Box(Vegetable(&quot;lettuce&quot;)) // funciona<br><br>val invalidFood: Box&lt;Vegetable&gt; = foodBox // 2. Não funciona<br><br>vegLeafBox.first() // Funciona</pre><p>No código acima há duas violações da covariância</p><ol><li>Substituição de tipos na escrita/entrada</li><li>Atribuição de supertipo à um subtipo</li></ol><p>Se não houvesse essa restrição (covariância só permitir leitura), poderíamos quebrar a segurança de tipos, por exemplo, colocando um Food genérico em um lugar que espera um Vegetable ou subtipo, gerando problemas em tempo de execução.</p><h4>Contravariância</h4><p>Aqui, um tipo genérico &lt;T&gt; é contravariante quando a substituição por supertipos é permitida, mas apenas na escrita, justamente o oposto da covariância.</p><pre>  open class Food(val name: String)<br>  class Vegetable(name: String) : Food(name)<br>  class Fruit(name: String) : Food(name)<br>  <br>  class Box&lt;in T&gt; {<br>      fun print(item: T) {<br>          println(item)<br>      }<br>  <br>      fun printWithTitle(item: T): T { // 1. Uso inválido de T no retorno<br>          println(&quot;----- list -----&quot;)<br>          println(item)<br>      }<br>  }<br>  <br>  // Removendo o trecho inválido (1) e chamando o método<br>  val foodBox: Box&lt;Food&gt; = Box&lt;Food&gt;() // funciona<br>  val vegetableBox: Box&lt;Vegetable&gt; = foodBox // funciona <br>  val fruitBox: Box&lt;Food&gt; = Box&lt;Fruit&gt;()// (2). Não funciona<br><br>  foodBox.print(Food(&quot;beans&quot;)) // Funciona<br>  foodBox.print(Fruit(&quot;apple&quot;)) // (3). Não funciona</pre><p>Já no código acima há outras duas da contravariância, inversas a covariância</p><ol><li>Substituição de tipos na leitura/saída</li><li>Em (2) e (3) há atribuição de subtipo à um supertipo</li></ol><p>Se não houvesse essa restrição (contravariância só permitir escrita), poderíamos quebrar a segurança de tipos, por exemplo, colocando um subtipo Fruit em um lugar que espera um tipo Vegetable ou supertipo, causando problemas em tempo de execução.</p><h3>Bônus</h3><p>No Kotlin, há uma palavra reservada: <strong>where.</strong> Apesar de não ter relação com os conceitos anteriores, ela pode ser combinada com <strong>in </strong>e <strong>out.</strong></p><p>Em resumo ela nos permite detalhar as restrições do tipo genérico, por exemplo:</p><pre>interface Food<br>interface Warm<br>interface Book<br><br>class MyBook: Book<br>class MyFood: Food<br>class WarmBox: Warm<br>class WarmFoodBox: Warm, Food<br><br>class Box&lt;T&gt;(val content: T) where T:Food, T:Warm<br><br>val box = Box(MyBook()) // Não funciona<br>val foodBox = Box(MyFood()) // Não funciona<br>val warmBox = Box(WarmBox()) // Não funciona<br>val warmFoodBox = Box(WarmFoodBox()) // Funciona</pre><p>Ou seja, nesse exemplo acima é como dizer: o conteúdo da caixa pode ser apenas do tipo FoodeWarm.</p><h4>Combinando com covariância</h4><pre><br>interface Food<br>interface Warm<br>interface Book<br><br>class MyBook: Book<br>class MyFood: Food<br>class WarmBox: Warm<br>class WarmFoodBox: Warm, Food<br><br>class Box&lt;out T&gt;(val content: T) where T : Food, T : Warm {<br>    fun first(): T = content<br>}<br><br>val box = Box(MyBook()) // Não funciona<br>val foodBox = Box(MyFood()) // Não funciona<br>val warmBox = Box(WarmBox()) // Não funciona<br>val warmFoodBox = Box(WarmFoodBox()) // Funciona<br><br>warmFoodBox.first() // Funciona</pre><h4>Combinando com contravariância</h4><pre>interface Food<br>interface Warm<br>interface Book<br><br>class MyBook: Book<br>class MyFood: Food<br>class WarmBox: Warm<br>class WarmFoodBox: Warm, Food<br><br>class Box&lt;in T&gt; where T : Food, T : Warm {<br>    fun show(item: T) {<br>        println(item)<br>    }<br>}<br>val box = Box&lt;MyBook&gt;() // Não funciona<br>val foodBox = Box&lt;MyFood&gt;() // Não funciona<br>val warmBox = Box&lt;WarmBox&gt;() // Não funciona<br>val warmFoodBox = Box&lt;WarmFoodBox&gt;() // Funciona<br><br>val warmFoodBox = Box&lt;WarmFoodBox&gt;() // Funciona<br><br>warmFoodBox.show(WarmFoodBox())</pre><h3>Conclusão</h3><p>Em suma, os conceitos discutidos definem o comportamento do tipo <em>Genérico</em> em relação a hierarquia de tipos, eles são essenciais em linguagens de programação com tipagem estática para garantir que nenhuma operação com tipagem inválida passe despercebida pelo compilador, gerando insegurança de tipos em tempo de execução.</p><p>Essa insegurança de tipo é justamente uma caraterística de linguagens com tipagem dinâmica como o JavaScript, Python e Ruby. O que não as tornam, de maneira alguma, linguagens inferiores, mas apenas linguagens com o objetivo oposto das estaticamente tipadas: flexibilidade.</p><p>E claro, essa flexibilidade traz mais liberdade, no entanto, com consequências: o risco de detectar problemas somente em tempo de execução. Por outro lado, linguagens estaticamente tipadas costumam ser mais “burocráticas com tipos”, ou seja, menos flexíveis, porém menos propensa a problemas detectados somente em tempo de execução.</p><p>A escolha entre elas depende do tipo de problema a ser resolvido, mas se escolher as estaticamente tipadas os conceitos de <strong>Invariância</strong>, <strong>Covariância</strong> e <strong>Contravariâcia </strong>no uso de <em>Generics</em> estarão presentes, mesmo que com diferenças sutis na forma como são aplicados em cada linguagem.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a803e2aeb486" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[WebAssembly: Rodando mais que JavaScript no navegador]]></title>
            <link>https://danlima-dev.medium.com/webassembly-rodando-mais-que-javascript-no-navegador-0feb97d151a7?source=rss-c263b528da4e------2</link>
            <guid isPermaLink="false">https://medium.com/p/0feb97d151a7</guid>
            <category><![CDATA[webassembly]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[front-end-development]]></category>
            <category><![CDATA[web-development]]></category>
            <dc:creator><![CDATA[Danilo Lima]]></dc:creator>
            <pubDate>Sun, 04 May 2025 01:00:39 GMT</pubDate>
            <atom:updated>2025-05-04T01:04:04.599Z</atom:updated>
            <content:encoded><![CDATA[<p>Nesse artigo, vamos bater um papo sobre Web Assembly, uma tecnologia que inovou a forma como código pode ser executado do lado cliente nas aplicações Web.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ClyIcJtGTdBNFnnRq1ivKQ.png" /></figure><h3>Contextualizando</h3><p>Web Assembly é uma compilação alvo portátil para linguagens de programação, similar ao Assembly, de baixo nível. Com ele é possível gerar, a partir de qualquer linguagem, código executável pelo browser, desde que exista um compilador adequado e um ambiente de execução adaptado.</p><p>Isso abre um leque de possibilidades, pois superamos a limitação de só poder executar JavaScript no navegador, obviamente que Web Assembly não existe como uma solução para substituí-lo, mas para complementá-lo.</p><p>Por muito tempo, a criação de interações ricas e performáticas na web, como jogos, processamento de imagens e animações 3D, que fosse performática e não comprometesse a experiência do usuário, era muito custoso de desenvolver, dado as características do próprio JavaScript e de como os navegadores funcionavam.</p><h4>Principais conceitos</h4><p>Antes de partirmos para uma abordagem mais prática, precisamos entender alguns conceitos fundamentais por trás do Web Assembly</p><p><strong>Módulo</strong></p><p>Ele representa um binário (o que foi gerado por uma linguagem origem) que foi compilado pelo navegador em um código de máquina, esse módulo pode ser compartilhado entre janelas e workers.</p><p><strong>Memória</strong></p><p>Um ArrayBuffer de tamanho variável, que contém o Array de bytes que são lidos e escritos pelas instruções de memória, de baixo nível, do Web Assembly.</p><p><strong>Tabela</strong></p><p>Um array tipado contendo referências, à funções, variáveis, etc.</p><p><strong>Instância</strong></p><p>Se trata de um módulo que contém todo o estado que utiliza em tempo de execução, como a memória e a tabela, além de um conjunto de valores importados. É similar a um ES module que é carregado em algum escopo global particular.</p><h4>Casos de uso</h4><p>Pra ter uma ideia do potencial dessa tecnologia, podemos listar alguns casos de uso reais</p><ul><li>Figma — Utiliza Web Assembly para renderizar gráficos e manipular imagens no navegador de forma muito rápida, já que esse tipo de atividade exige cálculos intensivos.</li><li>Squoosh — Ferramenta de compressão de imagem criada pelo Google, que usa Web Assembly para rodar codecs de imagem, como WebP, AVIF, etc. Diretamente no navegador.</li><li>Unity — Usa Web Assembly com WebGL pra rodar jogos e aplicações 3D no navegador.</li><li>Capcut Web — Escrito em C++, mas usa <a href="https://emscripten.org/">Emscripten</a> para compilar e código para Web Assembly, otimizando tarefas relacionadas a edição de vídeo no navegador</li><li>PyScript — Permite rodar python no browser, graças ao <a href="https://pyodide.org/en/stable/">Pyodide</a>.</li></ul><p>Esses são só alguns dos exemplos de uso do Web Assembly, existe uma lista extensa de outros casos de uso, disponível <a href="https://madewithwebassembly.com/">aqui</a>.</p><h3>Na prática</h3><p>Vamos colocar a mão na massa!</p><p>Criaremos um módulo usando Golang e o compilremos para Web Assembly, para que seja integrado em uma página web.</p><h4>Passo 1: Criando o módulo em Golang</h4><pre>// Crie um diretório<br>mkdir go-wasm<br><br>// Inicie um projeto Go<br>go mod init go-wasm</pre><p>Em seguida, vamos criar arquivo <strong>main.go</strong> e incluir nele o seguinte:</p><pre>package main<br><br>// da lib syscall/js, <br>import (<br>  // para trabalhar inteiros muito grandes<br> &quot;math/big&quot;<br>  // para fazer ponte entre o Go e o JavaScript<br> &quot;syscall/js&quot;<br>)<br><br>// Calculamos o fatorial de forma iterativa com o parâmetro passado <br>func factorial(this js.Value, param []js.Value) interface{} {<br> num := param[0].Int()<br> result := big.NewInt(1)<br><br> for i := 2; i &lt;= num; i++ {<br>  result.Mul(result, big.NewInt(int64(i)))<br> }<br><br> return js.ValueOf(result.String())<br>}<br><br>func main() {<br> c := make(chan struct{}, 0)<br><br> // Registramos a função factorial, que ficará disponível<br> // para ser chamada pelo código JavaScript<br> js.Global().Set(&quot;factorial&quot;, js.FuncOf(factorial))<br><br> // Precisamos disso para impedir que o programa Go finalize<br> &lt;-c<br>}</pre><h4>Passo 2: Compilando para Web Assembly</h4><p>Golang possui suporte a compilação para WASM desde a sua versão 1.11, e o processo é bem simples.</p><p>Com o compilador do Golang instalado, é só executar o comando abaixo no seu bash:</p><pre>GOOS=js GOARCH=wasm go build -o result/main.wasm</pre><p>Com isso, o binário main.wasm será gerado, contendo:</p><ul><li>As instruções do módulo que calcula o fatorial</li><li>Instruções Web Assembly, que são executadas por navegadores compatíveis</li><li>Estrutura do runtime do go embutida, como o garbage collector, por exemplo.</li></ul><h4>Passo 3: Integrando na nossa página</h4><p>Antes de começar, é fundamental destacar que precisamos do código JavaScript, que vem junto com a instalação do compilador do Go, para o para poder executar o binário compilado a partir do GO, se estiver usando bash pode copiá-lo para o diretório <strong>result</strong>/ do projeto.</p><pre>cp &quot;$(go env GOROOT)/misc/wasm/wasm_exec.js&quot; result</pre><p>Você também pode localizar a instalação do Go na sua máquina e copiar o arquivo direto para a pasta <strong>result/.</strong></p><p>Em seguida, crie um arquivo index.html em <strong>result/</strong>, contendo:</p><pre>&lt;!DOCTYPE html&gt;<br>&lt;html lang=&quot;en&quot;&gt;<br><br>&lt;head&gt;<br>    &lt;meta charset=&quot;UTF-8&quot;&gt;<br>    &lt;meta name=&quot;viewport&quot;<br>        content=&quot;width=device-width, initial-scale=1.0&quot;&gt;<br>    &lt;title&gt;GO - WASM&lt;/title&gt;<br>    &lt;script src=&quot;./wasm_exec.js&quot;&gt;&lt;/script&gt;<br>&lt;/head&gt;<br><br>&lt;body&gt;<br>    &lt;div&gt;<br>        &lt;h3 for=&quot;value&quot;&gt;Factorial&lt;/h3&gt;<br>        &lt;input type=&quot;text&quot; id=&quot;value&quot;&gt;<br>        &lt;button id=&quot;calculate&quot;&gt;Calculate&lt;/button&gt;<br>    &lt;/div&gt;<br>    &lt;br&gt;<br>    &lt;label&gt;Result: &lt;/label&gt;<br>    &lt;label id=&quot;result&quot;&gt;&lt;/label&gt;<br><br>    &lt;script&gt;<br>        function initWasm() {<br>            // Cria do gerenciador de execução do Go para WebAssembly<br>            const go = new Go();<br>            // Carrega o binário<br>            WebAssembly.instantiateStreaming(fetch(&quot;main.wasm&quot;), go.importObject).then((result) =&gt; {<br>                // Executa o código Go após a instância do WebAssembly<br>                // ser criada<br>                go.run(result.instance);<br>            });<br>        }<br><br>        initWasm();<br><br>        const button = document.getElementById(&#39;calculate&#39;);<br><br>        const formatOutput = (output) =&gt; {<br>            return String(output).replace(/\B(?=(\d{3})+(?!\d))/g, &quot;.&quot;);<br>        }<br><br>        const calculateFactorial = () =&gt; {<br>            const num = document.getElementById(&#39;value&#39;).value;<br>            // Realizamos a chamada de factorial, disponível globalmente,<br>            // após a execução de go.run, pois havia sido registrada<br>            // através do syscall/js citado anteriormente<br>            const result = BigInt(factorial(parseInt(num)));<br><br>            // Formatamos a visualização, que tem a leitura <br>            // comprometida para fatoriais muito grandes<br>            document.getElementById(&#39;result&#39;).innerHTML = formatOutput(result);<br>        }<br><br>        button.addEventListener(&#39;click&#39;, calculateFactorial);<br>    &lt;/script&gt;<br><br>&lt;/body&gt;<br><br>&lt;/html&gt;</pre><p>E ao interagir com a página após abrir a aplicação no navegador, temos algo como:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/567/1*iwbIwVemmMqh8Q8Rv2Ah3A.png" /></figure><p>Legal né? O nosso código JavaScript está interagindo com o código escrito em Go, que foi compilado para WASM.</p><p>Nesse exemplo, o código está sendo executado na thread principal do navegador, mas podemos ir além, imagina o que podemos alcançar ao combinar isso com Web Workers, mais, compilar a partir de outras linguagens, como C++ e Rust!</p><p>Mas isso talvez seja tema pra um próximo post do assunto.</p><h3>Conclusão</h3><p>Apesar da evolução de frameworks, bibliotecas, e do próprio JavaScript nos últimos anos, o que podia ser feito do lado cliente sempre esbarrava nas limitações impostas pelas características dos navegadores e do próprio JavaScript.</p><p>Web Assembly estendeu as capacidades do que pode ser executado no cliente, e combinado com outros API’s, hoje é case de sucesso em grandes projetos e empresas, é claro, ele continua em evolução, e assim como toda ferramenta não resolve todos os problemas, mas o fato dela existir abre o leque de como pensamos em soluções onde a performance do lado cliente é um critério importante.</p><p>Espero que tenha curtido o post, até logo!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=0feb97d151a7" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Angular: Real User Monitoring com Grafana Stack]]></title>
            <link>https://danlima-dev.medium.com/angular-real-user-monitoring-com-grafana-stack-27fb83465694?source=rss-c263b528da4e------2</link>
            <guid isPermaLink="false">https://medium.com/p/27fb83465694</guid>
            <category><![CDATA[observability]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[grafana]]></category>
            <category><![CDATA[front-end-development]]></category>
            <category><![CDATA[angular]]></category>
            <dc:creator><![CDATA[Danilo Lima]]></dc:creator>
            <pubDate>Fri, 06 Sep 2024 11:02:03 GMT</pubDate>
            <atom:updated>2024-09-06T11:02:03.162Z</atom:updated>
            <content:encoded><![CDATA[<p>Nesse post vamos abordar o tema de Observabilidade e RUM dentro do contexto de aplicações Frontend e entender como integrar essas ferramentas em aplicações Angular, vamos nessa!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_9W4zVgHioyi16i1DmNE3A.png" /></figure><h3>Contextualizando</h3><p>Observabilidade é a prática de coletar, analisar e entender dados gerados por um sistema para garantir que ele esteja funcionando corretamente e para identificar e solucionar problemas quando eles ocorrerem. Essa prática se baseia em três pilares, popularmente conhecidos com <em>Pilares da Observabilidade</em>, que são Métricas, Logs e Traces.</p><ul><li><strong>Métricas </strong>— São dados referentes à infraestrutura ou ao sistema, capturados em um período de tempo, que ajudam a compreender a saúde da aplicação e da infraestrutura. Como exemplos podemos citar taxa de erros de respostas, uso de memória, uso de CPU, etc.</li><li><strong>Logs — </strong>São registros detalhados de operações que ocorrem dentro da aplicação, muito importantes para compreender o que aconteceu dentro da aplicação em um determinado período.</li><li><strong>Traces — </strong>São informações referente ao percurso de uma requisição através dos diferentes componentes do sistema, que nos permite rastrear uma operação desde a sua origem até o seu destino.</li></ul><p>No contexto de aplicações web client-side o conceito ainda está caminhando apesar de ser bastante difundido em aplicações server-side, a falta de maturidade na aplicação desse conceito em aplicações Front-end leva a uma série de desafios no que diz respeito a garantir a performance, a satisfação dos usuários e à solução rápida de problemas.</p><p>Além disso, a falta de visão sobre como o usuário interage com a aplicação, por onde navega, onde clica, qual navegador utiliza e como a aplicação está realmente se comportando pode dificultar a investigação e solução de possíveis problemas, bem como o entendimento de qual aspecto da aplicação precisa ser melhorado de forma assertiva baseado em dados de usuários reais.</p><p>Felizmente, nos últimos anos tem surgido algumas ferramentas com esse objetivo, e hoje vamos falar sobre uma delas, o <strong>Grafana Faro</strong>!</p><h4>Grafana Faro</h4><p>O Grafana Faro é um SDK JavaScritpt Open Source, anunciado em 2022 pela Grafana Labs, que pode ser integrado em aplicações web para coletar dados de monitoramento de usuários reais, tais como métricas de performance, logs, eventos, exceções e traces.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*pPLRz3qODSyZL88Ojm2Gsw.png" /><figcaption><a href="https://grafana.com/static/assets/img/diagrams/grafana-oss-faro-diagram.svg">https://grafana.com/static/assets/img/diagrams/grafana-oss-faro-diagram.svg</a></figcaption></figure><p>Esses dados passam por um fluxo envolvendo ferramentas como o Grafana Loki, Grafana Tempo e os dashboards de visualização do Grafana. Além disso, o SDK pode enviar dados para diversas outras ferramentas de observabilidade.</p><h3>Integrando ao Angular</h3><p>Para tanto vamos dividir a tarefa em duas etapas:</p><ol><li>Primeiro precisamos configurar a infraestrutura que irá receber, processar e permitir que os dados sejam visualizados graficamente.</li><li>Integrar o SDK na nossa aplicação Angular para coletar e enviar os dados.</li></ol><h4>Preparando a infraestrutura</h4><p>Vamos utilizar o Docker para configurar os componentes necessários.</p><pre>version: &#39;3&#39;<br>services:<br>  # aqui configuramos o grafana como ferramenta de visualização dos dados<br>  grafana:<br>    image: grafana/grafana:latest<br>    environment:<br>      - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin<br>      - GF_AUTH_ANONYMOUS_ENABLED=true<br>      - GF_AUTH_BASIC_ENABLED=false<br>      - GF_FEATURE_TOGGLES_ENABLE=accessControlOnCall<br>      - GF_INSTALL_PLUGINS=https://storage.googleapis.com/integration-artifacts/grafana-lokiexplore-app/grafana-lokiexplore-app-latest.zip;grafana-lokiexplore-app<br>    ports:<br>      - 3000:3000/tcp<br><br>  # configuramos o grafana loki<br>  loki:<br>    image: grafana/loki:main<br>    command: &#39;-config.file=/etc/loki/loki-config.yaml&#39;<br>    ports:<br>      - 3100:3100<br>    volumes:<br>      - ./loki-config.yaml:/etc/loki/loki-config.yaml<br>  <br>  # configuramos o grafana alloy, que será o ponto<br>  # de entrada para onde o grafana faro irá enviar os dados<br>  faro_collector:<br>    image: grafana/alloy:latest<br>    ports:<br>      - 12345:12345<br>      - 3333:3333<br>    volumes:<br>      - ./config.alloy:/etc/alloy/config.alloy<br>    command: run --server.http.listen-addr=0.0.0.0:12345 --storage.path=/var/lib/alloy/data /etc/alloy/config.alloy<br>    depends_on:<br>      - tempo<br><br>  # definimos uma etapa de inicialização do grafana tempo, <br>  # criando um container temporário somente para configurar <br>  # permissões no volume<br>  init_tempo:<br>    image: &amp;tempo_image grafana/tempo:latest<br>    user: root<br>    entrypoint:<br>      - &quot;chown&quot;<br>      - &quot;10001:10001&quot;<br>      - &quot;/var/tempo&quot;<br>    volumes:<br>      - ./tempo-data:/var/tempo<br>  # e por fim configuramos o container do grafana tempo<br>  tempo:<br>    image: *tempo_image<br>    ports:<br>      - 3200:3200<br>      - 4318:4318<br>    volumes:<br>      - ./tempo.yaml:/etc/tempo.yaml <br>      - ./tempo-data:/var/tempo<br><br>    command: [ &quot;-config.file=/etc/tempo.yaml&quot; ]<br>    depends_on:<br>      - init_tempo</pre><p>Além disso, precisamos definir as configurações para o Grafana Loki, bem como para o Grafana Tempo. Porém, para não estender demais o artigo, vamos focar apenas na configuração do agente (arquivo <em>config.alloy</em>). As demais configurações estão disponíveis nesse <a href="https://github.com/danilolmc/angular-observability/tree/master/observability">repositório</a>.</p><p><strong>Configuração do agente</strong></p><pre>// configura o agente que irá receber os dados vindo do frontend<br>faro.receiver &quot;integrations_app_agent_receiver&quot; {<br>    server {<br>        cors_allowed_origins = [&quot;*&quot;]<br>        listen_address = &quot;0.0.0.0&quot;<br>        listen_port = 3333<br>        max_allowed_payload_size = &quot;10MiB&quot;<br><br>        rate_limiting {<br>            rate = 100<br>        }<br>    }<br><br>    output {<br>        // repassa os logs para a etapa de processamento e rotulamento<br>        logs   = [loki.process.add_label.receiver]<br>        // repassa os traces para a etapa de persistência no Grafana Tempo<br>        traces = [otelcol.exporter.otlphttp.trace_write.input]    <br>   }<br>}<br><br>// essa etapa é responsável por definir os rótulos criados a partir <br>// dos campos nos logs, para criar filtros no dashboard do grafana<br>loki.process &quot;add_label&quot; {<br>   stage.logfmt {<br>        mapping = {<br>            &quot;level&quot; = &quot;&quot;,<br>            &quot;kind&quot; = &quot;&quot;,<br>            &quot;type&quot;=&quot;&quot;,<br>            &quot;app_name&quot;=&quot;&quot;,<br>            &quot;message&quot;= &quot;&quot;,<br>            &quot;browser_name&quot;= &quot;&quot;,<br>            &quot;browser_version&quot; = &quot;&quot;,<br>        }<br>    }<br><br>    stage.labels {<br>        values = {<br>            &quot;level&quot; = &quot;level&quot;,<br>            &quot;kind&quot; = &quot;kind&quot;,<br>            &quot;type&quot; = &quot;type&quot;,<br>            &quot;app_name&quot; = &quot;app_name&quot;,<br>            &quot;message&quot; = &quot;message&quot;,<br>            &quot;browser_name&quot; = &quot;browser_name&quot;,<br>            &quot;browser_version&quot; = &quot;&quot;,<br><br>        }<br>    }<br>    // repassa para a etapa de persistência de logs no Grafana Loki<br>    forward_to = [loki.write.logs_write_client.receiver]<br>}<br><br>// envia os logs para o endpoint do servidor do Grafana oki<br>loki.write &quot;logs_write_client&quot; {<br>    endpoint {<br>        url = &quot;http://loki:3100/loki/api/v1/push&quot;<br>    }<br>}<br>// aqui, definimos o exportador usando uma configuração para o <br>// coletor do Open Telemetry, ele irá coletar os traces e enviá-los <br>// via HTTP para o Grafana Tempo<br>otelcol.exporter.otlphttp &quot;trace_write&quot; {<br><br>    client {<br>        endpoint = &quot;http://tempo:4318&quot;<br>        tls {  <br>            insecure             = true<br>            insecure_skip_verify = true<br>        }<br>    }<br>}</pre><p>A primeira etapa está finalizada, agora é só executar o comando abaixo e verificar se todos os containers estão de pé!</p><pre>docker compose up</pre><h4>Integrando o SDK ao Angular</h4><p>Vamos usar uma aplicação Angular de exemplo, disponível nesse repositório: <a href="https://github.com/danilolmc/angular-observability">https://github.com/danilolmc/angular-observability</a></p><p>O processo é bastante simples, precisamos somente inicializar o SDK antes que a aplicação Angular seja completamente carregada, por isso usamos o Injection Token APP_INITIALIZER.</p><pre>import { APP_INITIALIZER, ApplicationConfig, ErrorHandler, NgZone } from &#39;@angular/core&#39;;<br>import { TracingInstrumentation } from &#39;@grafana/faro-web-tracing&#39;;<br><br>import { provideHttpClient } from &#39;@angular/common/http&#39;;<br>import { provideRouter } from &#39;@angular/router&#39;;<br>import { routes } from &#39;./app.routes&#39;;<br>import { getWebInstrumentations, initializeFaro, MetaSession, ViewInstrumentation, WebVitalsInstrumentation } from &#39;@grafana/faro-web-sdk&#39;;<br>import { GlobalErrorHandler } from &#39;./shared/error/global.handler&#39;;<br><br><br>export const appConfig: ApplicationConfig = {<br>  providers: [<br>    provideRouter(routes),<br>    provideHttpClient(),<br>    provideApiEndpoint(),<br>    {<br>      provide: APP_INITIALIZER,<br>      useFactory: (zone: NgZone) =&gt; {<br>          return function () {<br>              zone.runOutsideAngular(() =&gt; {<br>                  initializeFaro({<br>                      url: &#39;http://localhost:3333/collect&#39;,<br>                      app: {<br>                          name: &#39;Product App&#39;,<br>                          version: &#39;1.0&#39;,<br>                      },<br>                      sessionTracking: {<br>                          enabled: true,<br>                          persistent: true,<br>                          maxSessionPersistenceTime: 1 * 60 * 2000,<br>                          onSessionChange: (oldSession: MetaSession | null, newSession: MetaSession) =&gt; {<br>                              console.log(`Session ${oldSession == null ? &#39;created&#39; : &#39;changed&#39;}`, { oldSession, newSession })<br>                          },<br>                      },<br>                      instrumentations: [<br>                          # desabilidando a captura de erros do console do usuário<br>                          ...getWebInstrumentations({<br>                              captureConsole: false,<br>                          }),<br>                          # instrumentação para gerar métricas do Core Web Vitals<br>                          new WebVitalsInstrumentation(),<br>                          # instrumentação para gerar dados de tracing<br>                          new TracingInstrumentation(),<br>                      ]<br>                  })<br>              });<br>          }<br>      },<br>      multi: true,<br>      deps: [NgZone]<br>  },<br>  {<br>      provide: ErrorHandler,<br>      useClass: GlobalErrorHandler<br>  }<br><br>  ]<br>};</pre><p>Detalhe que, como o SDK não consegue capturar automaticamente os erros lançados pelo Angular no console do navegador, precisamos configurar um handler para que ele seja disparado automaticamente quando um erro ocorrer.</p><p>Por fim, é só executar a aplicação e ver a mágica acontecer! Ao olhar na aba de Rede do DevTools do browser, você vai perceber que o SDK enviou automaticamente os dados de telemetria da aplicação Angular.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*pfDcJjloYKjgsLgj-e75MQ.png" /></figure><p>Para visualizar esses dados no Grafana é simples:</p><ul><li>Acesse a ferramenta de visualização do Grafana em <a href="http://localhost:3000">http://localhost:3000</a></li><li>Adicione o Grafana Loki e o Grafana tempo como fontes de dados (Data Sources) de acordo com a instrução no repositório: <a href="https://github.com/danilolmc/angular-observability?tab=readme-ov-file#setting-up-data-sources-in-grafana">https://github.com/danilolmc/angular-observability?tab=readme-ov-file#setting-up-data-sources-in-grafana</a></li><li>Após isso será possível visualizar os dados de tracing e de logs, métricas e afins, direto pela ferramenta Explorer do Grafana, interagindo com os filtros definidos na etapa de configuração do agente.</li><li>Você também pode configurar seu próprio dashboard gráfico a partir desses dados, ou importar um, como esse: <a href="https://github.com/grafana/faro-web-sdk/blob/main/dashboards/frontend-application.json">https://github.com/grafana/faro-web-sdk/blob/main/dashboards/frontend-application.json</a></li></ul><p>E esse é o resultado, após interagir com a aplicação Angular por alguns minutos:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BTGJ8PXZc6uG1H8-zpi9Ow.png" /></figure><p>Com isso, conseguimos visualizar diversos dados de um usuário real da aplicação, como métricas de Web Vitals, qual navegador o usuário está usando, em qual versão, número de erros acontecendo na aplicação, tipos de erros, número de acessos, quantas vezes o usuário iniciou e encerrou a sessão, dados detalhados de requisições e muito mais.</p><p>E não para por aí: o limite da customização depende do que você quer monitorar na sua aplicação, o que significa que dashboards ainda mais completos podem ser criados.</p><h3>Conclusão</h3><p>O Grafana Faro é uma ferramenta muito interessante no contexto abordado neste post, principalmente pela facilidade de integração e pelas possibilidades de extensão. Ele oferece uma visão mais palpável de como nossa aplicação se comporta diante de usuários reais. No entanto, é evidente que existe uma complexidade na manutenção desse tipo de infraestrutura. Apesar de soluções como o Grafana Cloud facilitarem a configuração e manutenção da infraestrutura para receber e lidar com dados de telemetria vindos do SDK, é importante avaliar a real necessidade desse tipo de solução para suas aplicações antes de considerar uma solução com essa complexidade.</p><p>Bem, é isso! A ideia era introduzir o tema, que ainda tem muito espaço para ser discutido no contexto de aplicações client-side. Espero que tenha curtido!</p><p>Repositório do projeto: <a href="https://github.com/danilolmc/angular-observability?tab=readme-ov-file#setting-up-data-sources-in-grafana">https://github.com/danilolmc/angular-observability</a></p><p>Mais sobre o Grafana Faro SDK: <a href="https://grafana.com/oss/faro/">https://grafana.com/oss/faro/</a></p><p>Mais sobre o Grafana Cloud: <a href="https://grafana.com/docs/grafana-cloud/monitor-applications/frontend-observability/">https://grafana.com/docs/grafana-cloud/monitor-applications/frontend-observability/</a></p><p>Até logo, dev! 😉</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=27fb83465694" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Angular: Testando Deferrable Views]]></title>
            <link>https://danlima-dev.medium.com/angular-testando-deferrable-views-1b45461c5e30?source=rss-c263b528da4e------2</link>
            <guid isPermaLink="false">https://medium.com/p/1b45461c5e30</guid>
            <category><![CDATA[deferrable-views]]></category>
            <category><![CDATA[testing]]></category>
            <category><![CDATA[angular]]></category>
            <category><![CDATA[testing-library]]></category>
            <category><![CDATA[jest]]></category>
            <dc:creator><![CDATA[Danilo Lima]]></dc:creator>
            <pubDate>Sat, 24 Aug 2024 17:43:26 GMT</pubDate>
            <atom:updated>2024-08-24T17:46:18.627Z</atom:updated>
            <content:encoded><![CDATA[<p>Nesse post vamos entender como testar um dos mais recentes recursos introduzidos no Angular, as Deferrable Views.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xqlgvAQDdpoTateClHOxZQ.png" /></figure><h4>Contextualizando</h4><p>Desde a versão 17 o Angular vem passando por um processo de modernização e renascimento, caracterizado pela introdução de uma série de recursos que buscam melhorar a experiência de desenvolvimento, diminuir a curva de aprendizado e tornar o framework mais performático e completo.</p><p>Um desses novos recursos foram as Deferrable Views, que nos permite fazer o lazy loading dos elementos da nossa aplicação a nível de template, por exemplo:</p><pre>@defer {<br>  &lt;app-list [posts]=&quot;posts()&quot;/&gt;<br>} @loading {<br>  &lt;p&gt;Carregando posts...&lt;/p&gt;<br>} @error {<br>  &lt;p&gt;Erro ao tentar carregar posts...&lt;/p&gt;<br>}</pre><p>Usamos a declaração @defer no nosso template para indicar que tudo que está no bloco entre a sua chave de abertura e fechamento deve ser carregado de forma tardia, nesse exemplo estamos adiando o carregamento de um componente que renderiza uma lista até que os dados estejam disponíveis para ser renderizado.</p><p>Podemos ainda declarar um bloco @loading para renderizar um estado de carregamento e um bloco @error para dar um feedback ao usuário caso houver problema ao carregar o componente de lista.</p><h4>Testando Deferrable Views</h4><p>Bem, toda essa inclusão de recursos levanta a discussão sobre testes, e com as Deferrable Views não é diferente, e é disso que vamos aprofundar agora.</p><p>O Angular gerencia a renderização dos blocos no contexto das Deferrable Views, ele decide quando cada bloco será renderizado para o usuário, no entanto, num contexto de teste onde queremos reproduzir e validar os comportamentos da nossa View o ideal é que tenhamos mais controle sobre isso.</p><p>Felizmente o Angular disponibiliza uma API que nos permite disparar os diferentes estados respectivos a cada um desses blocos, por exemplo:</p><pre><br>// Renderiza o estado final<br>await deferBlockFixture.render(DeferBlockState.Complete);<br><br>// Renderiza o estado de loading respectivo ao bloco @loading<br>await deferBlockFixture.render(DeferBlockState.Loading);<br><br>// Renderiza o estado de erro respectivo ao bloco @error<br>await deferBlockFixture.render(DeferBlockState.Error);<br><br>// Não incluímos no nosso exemplo, mas <br>// caso tivesse poderiamos renderizar o estado respectivo <br>// ao bloco @placeholder<br>await deferBlockFixture.render(DeferBlockState.Placeholder);</pre><p>Basicamente, podemos controlar a renderização manualmente e consequentemente controlar o comportamento da nossa View, vamos ver um exemplo completo agora…</p><h4>Preparando o ambiente</h4><p>Vamos implementar alguns testes no nosso componente anterior, que serão o seguinte:</p><ul><li>Testar o cenário feliz, onde a lista de posts foi renderizada;</li><li>Testar o cenário infeliz, em que houve um erro ao renderizar a lista;</li><li>Testar o cenário de carregamento, onde o componente de lista ainda está sendo preparado para renderizar;</li><li>E um plus, testando cenário onde a lista carrega com sucesso, porém está vazia.</li></ul><p>Porém, antes de começar, precisamos configurar o ambiente, vamos usar o seguinte biblioteca Angular Testing Library e o Jest, para configurar no seu ambiente use os links abaixo:</p><ul><li>Jest: <a href="https://timdeschryver.dev/blog/integrate-jest-into-an-angular-application-and-library#">https://timdeschryver.dev/blog/integrate-jest-into-an-angular-application-and-library#</a></li><li>Angular Testing Library: <a href="https://timdeschryver.dev/blog/good-testing-practices-with-angular-testing-library#">https://timdeschryver.dev/blog/good-testing-practices-with-angular-testing-library#</a></li></ul><h4>Escrevendo os testes</h4><p>Bora pro primeiro teste, nosso componente responsável por renderizar a lista usa um serviço que requisita dados de uma API, vamos criar um mock para o nosso serviço:</p><p><strong>Criando nosso mock</strong></p><pre>const mockedPosts: Post[] = [<br>  {<br>    &quot;id&quot;: 1,<br>    &quot;title&quot;: &quot;First Post&quot;,<br>    &quot;body&quot;: &quot;This is the first post&quot;,<br>    &quot;tags&quot;: [],<br>    &quot;reactions&quot;: {<br>      &quot;likes&quot;: 192,<br>      &quot;dislikes&quot;: 25<br>    },<br>    &quot;views&quot;: 305,<br>    &quot;userId&quot;: 121<br>  },<br>  {<br>    &quot;id&quot;: 2,<br>    &quot;title&quot;: &quot;Second Post&quot;,<br>    &quot;body&quot;: &quot;This is the second post&quot;,<br>    &quot;tags&quot;: [],<br>    &quot;reactions&quot;: {<br>      &quot;likes&quot;: 192,<br>      &quot;dislikes&quot;: 25<br>    },  <br>    &quot;views&quot;: 305,<br>    &quot;userId&quot;: 121<br>  },<br>]</pre><p><strong>Configurando o teste</strong></p><pre>describe(&#39;AppComponent&#39;, () =&gt; {<br>  const defaultConfig = {<br>    // Perceba que precisamos mudar o comportamento dos blocos defer <br>    // para manual<br>    deferBlockBehavior: DeferBlockBehavior.Manual,<br>    providers: [<br>      {<br>        provide: PostService,<br>        useValue: {<br>          getPosts() {<br>            return of({<br>              limit: 10,<br>              posts: mockedPosts,<br>              skip: 0,<br>              total: mockedPosts.length<br>            } as GetPostsResponse)<br>          }<br>        }<br>      }<br>    ],<br>  }<br>// ...</pre><p><strong>Testando o cenário feliz — lista renderizada</strong></p><pre>  it(&#39;should display post list&#39;, async () =&gt; {<br><br>    const { fixture } = await render(AppComponent, {<br>        ...defaultConfig<br>    });<br><br>    // Obtemos referência ao nosso bloco @defer no template<br>    const deferBlockFixture = (await fixture.getDeferBlocks())[0];<br><br>    // Renderizamos o estado final<br>    await deferBlockFixture.render(DeferBlockState.Complete);    <br><br>    // Verificamos se os dois itens da lista estão renderizados<br>    expect(screen.getByText(mockedPosts[0].title)).toBeInTheDocument();<br>    expect(screen.getByText(mockedPosts[1].title)).toBeInTheDocument();<br>  });</pre><p><strong>Testando cenário de carregamento</strong></p><pre>  it(&#39;show display loading message while posts are still being fetched&#39;, async () =&gt; {<br><br>    const { fixture } = await render(AppComponent, {<br>        ...defaultConfig<br>    });<br><br>    const deferBlockFixture = (await fixture.getDeferBlocks())[0];<br><br>    await deferBlockFixture.render(DeferBlockState.Loading);    <br><br>    expect(screen.getByText(/Carregando posts.../i)).toBeInTheDocument();<br>  });</pre><p><strong>Testando o cenário infeliz — erro ao carregar posts</strong></p><pre>it(&#39;show display error message when fetching posts returns an error&#39;, async () =&gt; {<br><br>    // Sobrescrevemos o provider para simular<br>    // um erro vindo do PostService usando a função throwError do RxJS<br>    const { fixture } = await render(AppComponent, {<br>        ...defaultConfig,<br>        providers: [<br>          {<br>            provide: PostService,<br>            useValue: {<br>              getPosts(){<br>                return throwError(() =&gt; new Error(&#39;Error on loading posts&#39;))<br>              }<br>            }<br>          }<br>        ]<br>    });<br><br>    const deferBlockFixture = (await fixture.getDeferBlocks())[0];<br><br>    await deferBlockFixture.render(DeferBlockState.Error);    <br>     <br>    const errorMsgRegex = /Erro ao tentar carregar posts.../i;<br><br>    expect(screen.getByText(errorMsgRegex)).toBeInTheDocument();<br>  });</pre><p><strong>Testando cenário de lista vazia</strong></p><p>O nosso componente de lista utiliza o novo Control Flow do Angular, que foi um dos recursos introduzidos nas novas versões.</p><p>Aqui usamos a declaração @for para iterar sobre a lista de posts e renderizar cada um dos itens, o interessante é que podemos definir um bloco @empty, que renderiza um conteúdo quando a lista estiver vazia.</p><pre>&lt;ul&gt;<br>  @for (item of posts(); track item) {<br>    &lt;li data-test=&quot;post&quot;&gt;<br>      &lt;h2&gt;{{item.title}}&lt;/h2&gt;<br>      &lt;p&gt;{{item.body}}&lt;/p&gt;<br>      &lt;div&gt;<br>        &lt;span&gt;Views: {{item.views}}&lt;/span&gt;<br>        &lt;span&gt;Likes {{item.reactions.likes}}&lt;/span&gt;<br>      &lt;/div&gt;<br>    &lt;/li&gt;<br>  } @empty {<br>    &lt;p&gt;Nenhum post para ser exibido&lt;/p&gt;<br>  }<br>  &lt;/ul&gt;</pre><p>E para testar, só precisamos renderizar o estado final do bloco @defer:</p><pre>  it(&#39;show display empty state message when fetching posts returns an empty list&#39;, async () =&gt; {<br><br>    const { fixture } = await render(AppComponent, {<br>        ...defaultConfig,<br>        // Sobrescrevemos o PostService para retornar uma lista vazia<br>        providers: [<br>          {<br>            provide: PostService,<br>            useValue: {<br>              getPosts(){<br>                return of([])<br>              }<br>            }<br>          }<br>        ]<br>    });<br><br>    const deferBlockFixture = (await fixture.getDeferBlocks())[0];<br><br>    // Renderizando o estado completo do bloco<br>    await deferBlockFixture.render(DeferBlockState.Complete);    <br>   <br>    const emptyStateMsg = /Nenhum post para ser exibido/i;<br><br>    expect(screen.getByText(emptyStateMsg)).toBeInTheDocument();<br>  });</pre><p>Resultado dos nossos testes:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/579/1*PKBTgY8UfMTepycs7-GqtQ.png" /></figure><h3>Conclusão</h3><p>As Deferrable Views são um dos melhores recursos incluídos no Angular nos últimos tempos, com ela podemos adiar o carregamento de componentes, diretivas, pipes e qualquer CSS associado a eles.</p><p>E adiar o carregamento também significa que o Angular cria um bundle separado para esses elementos. A <a href="https://angular.dev/guide/defer">API</a> é muito poderosa e recomendo dar uma olhada com mais profundidade, podemos ir muito além, só para mencionar, podemos adiar o carregamento de uma View até a iteração do usuário com a UI, por exemplo.</p><p>Os testes nesse recurso também traz desafios, mas com uma API simples a experiência de desenvolvimento dos testes se torna mais suave!</p><p>E ai, o que achou ?</p><p>Se quiser apoiar o meu trabalho reaja a esse post se te ajudei de alguma forma, compartilhe e me siga por aqui ou nas outras redes pra ficar por dentro dos próximos conteúdos.</p><p>Até logo, dev! 😉</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1b45461c5e30" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Componentes desacoplados com Bridge Pattern no Angular]]></title>
            <link>https://danlima-dev.medium.com/componentes-desacoplados-com-bridge-pattern-no-angular-879b2dd88bbb?source=rss-c263b528da4e------2</link>
            <guid isPermaLink="false">https://medium.com/p/879b2dd88bbb</guid>
            <category><![CDATA[design-patterns]]></category>
            <category><![CDATA[angular]]></category>
            <category><![CDATA[best-practices]]></category>
            <category><![CDATA[front-end-development]]></category>
            <dc:creator><![CDATA[Danilo Lima]]></dc:creator>
            <pubDate>Thu, 22 Aug 2024 15:10:44 GMT</pubDate>
            <atom:updated>2024-08-22T15:12:19.547Z</atom:updated>
            <content:encoded><![CDATA[<p>Entenda o segredo por trás de componentes extensíveis, reutilizáveis e desacoplados.</p><p>Nesse post vamos entender como o Design Pattern Bridge, junto com alguns recursos do Angular pode tornar os componentes da nossa aplicação altamente reutilizáveis e flexíveis, diminuindo o acoplamento.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*I_y6WM2QgsehgACfF3bjXg.png" /></figure><h4>Antes de mais nada… o que é o Design Pattern Bridge?</h4><p>Ele é um Design Pattern estrutural que permite separar abstração da implementação e evoluí-las de forma independente. A ideia é evitar o acoplamento direto entre abstrações e suas implementações.</p><p>Tomemos por exemplo um fluxo de escolha do método de pagamento:</p><pre>interface PaymentMethod {<br>  process_payment(): void;<br>}<br><br>interface PaymentProcessor {<br>  process(): void;<br>}<br><br>class CardPaymentProcessor implements PaymentProcessor {<br>  process(): void {<br>   console.log(&#39;Prosseguindo para a inserção dos dados do cartão...&#39;);<br>  }<br>}<br><br>class PixPaymentProcessor implements PaymentProcessor {<br>  process(): void {<br>    console.log(&#39;Gerando o código pix para pagamento...&#39;);  <br>  }<br>}<br><br>class BoletoPaymentProcessor implements PaymentProcessor {<br>  process(): void {<br>    console.log(&#39;Gerando o boleto para pagamento...&#39;);<br>  }<br>}<br><br>class PaymentMethodImpl implements PaymentMethod {<br>  constructor(paymentProcessor: PaymentProcessor){}<br><br>  process_payment(){<br>    this.paymentProcessor.process();<br>  }<br>}<br><br><br>const cardPaymentMethod = new CardPaymentMethod();<br>const paymentMethodImpl = new PaymentMethodImpl(cardPaymentMethod);<br><br>paymentMethodImpl.process_payment();</pre><p>Perceba que no exemplo acima, podemos trocar a implementação de PaymentMethodImpl sem mexer nas classes que implementa o PaymentProcessor e também passá-las para a implementação do método de pagamento, tornando nosso código bastante flexível a mudanças.</p><p>Agora vamos ver como implementar esse mesmo padrão no Angular, usando um fluxo de escolha de método de pagamento.</p><p>Para isso vamos precisar resolver os seguintes desafios:</p><ul><li>Precisamos de componentes que vão representar um PaymentProcessor para os diferentes métodos de pagamento (Boleto, Pix, etc.);</li><li>Também vamos precisar de um componente orquestrador;</li><li>O usuário deve ser capaz de escolher um método de pagamento e clicar em prosseguir;</li><li>E o mais importante, precisamos que o componente orquestrador se comunique com os orquestrados.</li></ul><h4>Componente de método de pagamento</h4><pre>export interface PaymentProcessorComponent {<br>    METHOD: PaymentMethod<br>    process(): void;<br>    parentContainer: ControlContainer;<br><br>    get formGroup(): FormGroup;<br>}<br><br>@Component({<br>  selector: &#39;app-payment-method[pix]&#39;,<br>  standalone: true,<br>  imports: [ReactiveFormsModule],<br>  template: `<br>      &lt;ng-container [formGroup]=&quot;formGroup&quot;&gt;<br>        &lt;label for=&quot;payment-method-pix&quot;&gt;<br>            &lt;input type=&quot;radio&quot;<br>                value=&quot;pix&quot;<br>                name=&quot;payment_method&quot;<br>                id=&quot;payment-method-pix&quot;<br>                formControlName=&quot;payment_method&quot;&gt;<br>            &lt;ng-content /&gt;<br>        &lt;/label&gt;<br>    &lt;/ng-container&gt;<br>  `,<br>})<br>export class PixMethodPaymentComponent implements PaymentProcessorComponent {<br><br>  METHOD: PaymentMethod = &#39;pix&#39;;<br>  parentContainer = inject(ControlContainer);<br><br>  get formGroup() {<br>    return this.parentContainer.control as FormGroup;<br>  }<br><br>  public process(): void {<br>    console.log(&#39;Gerando o código pix para pagamento...&#39;);<br>  }<br>}</pre><p>Todos os componentes que representam um método de pagamento devem implementar a interface PaymentProcessorComponent, essa interface força esses componentes a terem um contrato único:</p><ul><li>Um METHOD, indicando qual o método de pagamento;</li><li>Uma propriedade parentContainer para acessar o formulário que estará dentro do contexto do elemento pai onde esse componente for inserido;</li><li>Um método <strong>get </strong>que retorna a referência do formulário que está dentro do componente pai;</li><li>E por último, o método para processar o pagamento desse método de pagamento em si</li></ul><h4>Componente orquestrador</h4><pre>// ... importações omitidas<br><br>export type PaymentMethod = [&#39;cartao&#39;, &#39;pix&#39;, &#39;boleto&#39;][number]<br>export const PAYMENT_METHOD = new InjectionToken&lt;PaymentProcessorComponent&gt;(&#39;PAYMENT_METHOD&#39;)<br><br>@Component({<br>  selector: &#39;app-payment-wrapper&#39;,<br>  standalone: true,<br>  imports: [ReactiveFormsModule],<br>  template: `<br>    &lt;div&gt;<br>      &lt;h2&gt;Método de pagamento&lt;/h2&gt;<br>      &lt;h6&gt;Selecione um método de pagamento para prosseguir&lt;/h6&gt;<br>      <br>      &lt;ng-content&gt;&lt;/ng-content&gt;<br>      &lt;button (click)=&quot;process_choice()&quot;&gt;Prosseguir&lt;/button&gt;<br>    &lt;/div&gt;<br>  `,<br>  styleUrl: &#39;./payment-wrapper.component.scss&#39;,<br>})<br>export class PaymentWrapperComponent {<br>  formGroupDirective = inject(FormGroupDirective);<br>  paymentMethods = contentChildren(PAYMENT_METHOD);<br><br>  process_choice() {<br><br>    if(!this.paymentMethods().length) {<br>      console.log(&#39;Nenhum método de pagamento encontrado&#39;);<br>      return;<br>    }<br><br>    if(this.formGroupDirective){<br>      const selectedPaymentMethod = this.formGroupDirective?.control.get(&#39;payment_method&#39;)?.value<br>      const method = this.paymentMethods().find(paymentOption =&gt; paymentOption.METHOD == selectedPaymentMethod);<br>      if(!method){<br>        console.log(&#39;Nenhuma opção de pagamento selecionada!&#39;)<br>        return;<br>      }<br>      method.process();<br>    }<br>  }<br>}</pre><p>Esse componente será responsável por descobrir a forma de pagamento escolhida pelo usuário e chamar o método correspondente. Ele fará isso obtendo a lista dos componentes projetados que implementam a interface associada ao injection token PAYMENT_METHOD quando o botão Prosseguir for clicado.</p><p>Para ver isso em ação vamos declarar o nosso template no componente app-root:</p><pre>import { Component, inject } from &#39;@angular/core&#39;;<br>import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule } from &#39;@angular/forms&#39;;<br>import { CardPaymentMethodComponent } from &#39;./payments-method/card/card.component&#39;;<br>import { PixMethodPaymentComponent } from &quot;./payments-method/pix/pix.component&quot;;<br>import { PaymentMethod, PaymentWrapperComponent } from &#39;./payment-options/payment-wrapper/payment-wrapper.component&#39;;<br>import { BoletoPaymentMethodComponent } from &#39;./payments-method/boleto/boleto.component&#39;;<br><br>@Component({<br>  selector: &#39;app-root&#39;,<br>  standalone: true,<br>  imports: [<br>    ReactiveFormsModule,<br>    PaymentWrapperComponent,<br>    PixMethodPaymentComponent,<br>  ],<br>  template: `<br>    &lt;app-payment-wrapper [formGroup]=&quot;form&quot;&gt;<br>      &lt;app-payment-method pix&gt;PIX&lt;/app-payment-method&gt;<br>    &lt;/app-payment-wrapper&gt;<br>  `,<br>  styleUrl: &#39;./app.component.scss&#39;<br>})<br>export class AppComponent {<br>  form: FormGroup;<br>  formBuilder = inject(FormBuilder);<br><br>  ngOnInit(): void {<br>    this.form = this.formBuilder.group({<br>      payment_method: new FormControl&lt;PaymentMethod&gt;(&#39;pix&#39;),<br>    })<br>  }<br><br>}</pre><p>Ao executar a sua aplicação você verá um formulário customizado, com a opção de PIX pré-selecionada:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/380/1*KGxRMNbjPeL6g1scrcgTCg.png" /></figure><p>Porém, ao clicar em Prosseguir a mensagem “Nenhum método de pagamento encontrado” aparecerá no console do navegador.</p><p>Mas por que isso? Como que o componente pai vai obter os componentes filhos sem referenciar as suas respectivas implementações, consequentemente criando acoplamento?</p><p>O segredo está relacionado a como a Injeção de Dependências funciona no Angular, mais especificamente ao mapeamento de dependências. Por exemplo, é comum declararmos um serviço como dependência de componentes para que o sistema de injeção de dependências do Angular forneça uma instância dele automaticamente.</p><pre>@Component({<br>  selector: &#39;app-payment-method[pix]&#39;,<br>  standalone: true,<br>  imports: [ReactiveFormsModule],<br>  template: `<br>    &lt;p&gt;pix&lt;/p&gt;<br>  `,<br>  providers: [PaymentService]<br>})<br>export class PixMethodPaymentComponent {}</pre><p>O Angular possui configuradores de providers: useClass, useValue, useFactory e useExisting. Com eles podemos fazer o mapeamento das dependências do nosso componente, por exemplo:</p><pre>@Component({<br>  selector: &#39;app-payment-method[pix]&#39;,<br>  standalone: true,<br>  imports: [ReactiveFormsModule],<br>  template: `<br>    &lt;p&gt;pix&lt;/p&gt;<br>  `,<br>  providers: [<br>     { provide: PaymentService, useValue: &#39;This is a replacement&#39;}<br>  ]<br>})<br>export class PixMethodPaymentComponent {}</pre><p>Dessa forma, toda vez que for solicitado uma instância do serviço PaymentService, será fornecido o valor definido em useValue. Porém, esses configurares de providers não se limita a valores estáticos, e é aí que está o a dica para resolver o nosso problema anterior, associando o nosso componente ao Injection token PAYMENT_METHOD:</p><pre>@Component({<br>  selector: &#39;app-payment-method[pix]&#39;,<br>  standalone: true,<br>  imports: [ReactiveFormsModule],<br>  template: `<br>    &lt;p&gt;pix&lt;/p&gt;<br>  `,<br>  providers: [<br>     { provide: PAYMENT_METHOD, useExisting: PixMethodPaymentComponent }<br>  ]<br>})<br>export class PixMethodPaymentComponent {}</pre><p>Agora, ao executar a nossa aplicação novamente, tudo funcionará como o esperado, veremos a mensagem “Gerando código pix para pagamento…” no console do browser:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/562/1*B672rouYbDKt5slssb8czA.png" /></figure><p>Com isso, podemos incluir, remover ou modificar diversos outros componentes que implementam outras opções de pagamentos, como pagamento por cartão, boleto… e desde que eles sigam a interface tudo funcionará bem, como no exemplo a seguir:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/546/1*T5fX6rMo1Zxq7PVlBmF00w.png" /></figure><p>Se quisermos flexibilizar ainda mais, podemos criar uma abstração para o componente orquestrador app-payment-wrapper, assim poderíamos trocá-lo por um componente concreto, que implemente a abstração, e ele continuará funcionando sem precisarmos mexer nos componentes filhos e vice-versa.</p><h3>Conclusão</h3><p>O time do Design Pattern Bridge, ControlContainer, Projeção de conteúdo e Injeção de Dependências no Angular é uma combinação poderosíssima, com ela podemos criar composições de componentes extremamente flexíveis, fácil de modificar e de testar. Com esse Pattern diminuímos o acoplamento entre os componentes, fazendo com que eles só precisem obedecer aos contratos estabelecidos pelas suas respectivas abstrações.</p><p>Até logo, dev! 😉</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=879b2dd88bbb" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Aplicando conceitos de Arquitetura Hexagonal no Angular]]></title>
            <link>https://danlima-dev.medium.com/aplicando-conceitos-de-arquitetura-hexagonal-no-angular-d589688f9950?source=rss-c263b528da4e------2</link>
            <guid isPermaLink="false">https://medium.com/p/d589688f9950</guid>
            <category><![CDATA[ports-and-adapters]]></category>
            <category><![CDATA[front-end-development]]></category>
            <category><![CDATA[testing]]></category>
            <category><![CDATA[hexagonal-architecture]]></category>
            <category><![CDATA[angular]]></category>
            <dc:creator><![CDATA[Danilo Lima]]></dc:creator>
            <pubDate>Thu, 27 Jun 2024 21:06:34 GMT</pubDate>
            <atom:updated>2024-06-28T14:16:14.265Z</atom:updated>
            <content:encoded><![CDATA[<p>Nesse post iremos explorar como podemos aplicar os conceitos da famosa Arquitetura de “Ports and Adapters” e discutir se, e como, os seus fundamentos podem ser úteis para escrever uma aplicação Angular mais desacoplada e fácil de manter, que dê gosto de codificar, vamos nessa!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*856SiCnOfeKWYFWgB-5ZKA.png" /></figure><p>Antes de adentrarmos na aplicação prática dos conceitos, precisamos entender do que se trata a arquitetura hexagonal, que em resumo:</p><blockquote>É um padrão de design de software que busca promover a separação clara entre a lógica de negócio e os aspectos externos da aplicação.</blockquote><p>Ao usar essa abordagem usamos alguns componentes fundamentais que promovem o objetivo de tornar a lógica de negócio independe de tecnologia, são eles:</p><ul><li><strong>Camada de aplicação/domínio</strong>: Aqui reside a lógica de negócio com as regras mais fundamentais do sistema, que é independente de tecnologia e do mundo exterior à aplicação.</li><li><strong>Portas</strong>: São interfaces que definem as portas de entrada e saída para o núcleo mais interno da aplicação, ou seja, o domínio. Elas são usadas pelo domínio da aplicação para interagir com o mundo externo e vice-versa.</li><li><strong>Adaptadores: </strong>São implementações das portas, eles traduzem as chamadas do domínio à recursos externos e vice-versa.</li><li><strong>Drivers: </strong>Os drivers são os atores primários e externos, que interagem com a camada de aplicação, são por exemplo, aplicações SPAs, Filas de mensagem, casos de teste, aplicativos e etc.</li><li><strong>Driven Actors: </strong>Os driven actors são os atores, também externos, com quem a camada de aplicação vai interagir, como por exemplo, um banco de dados, uma aplicação de terceiros, uma fila de mensagem, um servidor de e-mail e etc.</li></ul><p>Entendido o conceito, é hora de colocar a mão na massa.</p><p>Vamos criar uma pequena aplicação Angular que lista alguns produtos e permite que sejam adicionados ao carrinho de compras, vamos distribuir os componentes, diretivas e serviços de forma que a arquitetura hexagonal fique gritante no nosso código e depois vamos discutir o que fizemos.</p><h3><strong>Camada de aplicação</strong></h3><p>Na arquitetura hexagonal, essa é a camada mais interna, onde fica as regras de negócio, é a camada de domínio, com toda lógica que é independente do framework, aqui também fica os nossos “use cases”.</p><p>Nossa aplicação possui duas entidades de domínios a princípio, uma de Produto e outra de Carrinho, além de dois respectivos casos de uso, um para listar produtos e outro para adicionar um produto ao carrinho.</p><h4>Entidade de Carrinho</h4><pre>import { Product } from &#39;./product&#39;;<br><br>export interface CartProduct { product: Product; amountOnCart: number; }<br><br>export class Cart {<br><br>    constructor(public cartList: CartProduct[], private _cartSubTotal = 0) { }<br><br>    get subtotal() {<br>        return this._cartSubTotal;<br>    }<br><br>    set subtotal(total: number) {<br>        this._cartSubTotal = total;<br>    }<br><br>    addToCart(productToAddOnCart: Product) {<br><br>        const productOnCart = this.cartList.find(item =&gt; item.product.id == productToAddOnCart.id) ?? null;<br><br>        const hasStock = productToAddOnCart.isAvailable();<br>        console.log(hasStock)<br><br>        if (!productToAddOnCart || !hasStock) return null;<br><br>        let product: CartProduct | null = null<br><br>        if (productOnCart) {<br>            productOnCart.amountOnCart = productOnCart.amountOnCart + 1;<br>            product = productOnCart;<br>        }<br><br>        if (!productOnCart) {<br>            const newCartProduct: CartProduct = { product: productToAddOnCart, amountOnCart: 1 }<br>            this.cartList.push(newCartProduct);<br>            product = newCartProduct;<br>        }<br><br>        this.subtotal = this.calcSubtotal();<br><br>        return product;<br>    }<br><br>    private calcSubtotal() {<br>        const total = this.cartList.reduce((acc, curr) =&gt; {<br>            return acc + (curr.amountOnCart * curr.product.price)<br>        }, this._cartSubTotal)<br><br>        return total;<br>    }<br><br><br>    getCartProduct(productId: number) {<br>        const targetProduct = this.cartList.find((item) =&gt; item.product.id === productId);<br><br>        if (!targetProduct) return null;<br><br>        return targetProduct;<br>    }<br>}</pre><p>Essa entidade contém toda a regra de negócio para que um produto seja adicionado ao carrinho, validando se o produto está em estoque, incrementando a quantidade do produto caso ele já esteja no carrinho, além de manter subtotal atualizado.</p><p>Podemos incluir outros métodos como para remover um produto do carrinho, esvaziar o carrinho… aqui é a camada onde definimos as regras que qualquer um interessado em interagir com essa entidade precisará seguir.</p><h4>Entidade de Produto</h4><pre>export interface Dimensions { width: number; height: number; depth: number; }<br><br>export interface Review {<br>  rating: number;<br>  comment: string;<br>  date: string;<br>  reviewerName: string;<br>  reviewerEmail: string;<br>}<br><br>export interface Meta {<br>  createdAt: string;<br>  updatedAt: string;<br>  barcode: string;<br>  qrCode: string;<br>}<br><br>export class Product {<br><br>  constructor(  <br>    private _id: number,<br>    private _title: string,<br>    private _price: number,<br>    private _stock: number,<br>    private _description?: string,<br>    private _thumbnail?: string,<br>  ) {}<br><br>  public isAvailable(): boolean {<br>    return this._stock !== undefined &amp;&amp; this._stock &gt; 0;<br>  }<br><br>  get id(): number {<br>    return this._id;<br>  }<br><br>  get title(): string {<br>    return this._title;<br>  }<br><br>  get price(): number {<br>    return this._price;<br>  }<br><br>  get description(): string | undefined {<br>    return this._description;<br>  }<br><br>  get stock(): number {<br>    return this._stock;<br>  }<br><br>  get thumbnail(): string | undefined {<br>    return this._thumbnail;<br>  }<br><br>  set id(id: number) {<br>    this._id = id;<br>  }<br><br>  set title(title: string) {<br>    this._title = title;<br>  }<br><br>  set thumbnail(thumbnail: string) {<br>    this._thumbnail = thumbnail;<br>  }<br><br><br>  set price(price: number) {<br>    if (this._price &lt; 0) {<br>      throw new Error(&#39;Price cannot be negative&#39;);<br>    }<br>    this._price = price;<br>  }<br><br>  set description(description: string) {<br>    this._description = description;<br>  }<br><br>  set stock(stock: number) {<br>    if (stock &lt; 0) {<br>      throw new Error(&#39;Stock cannot be negative&#39;);<br>    }<br>    this._stock = stock;<br>  }<br>}    </pre><p>Nossa entidade de produtos é bem simples, apenas com os métodos getters/setters que encapsulam a definição e recuperação de atributos da nossa classe.</p><h3><strong>Portas</strong></h3><p>As portas são interfaces que vão definir como o mundo externo interage com o a camada de domínio da aplicação e como essa camada interage com o mundo externo. Os adaptadores irão implementar essas portas, dessa forma a camada de domínio fica totalmente agnóstica, e não importa quem está se comunicando com o núcleo da aplicação e vice versa, desde que respeite essas portas.</p><p>A principio, vamos definir duas delas para cada entidade de domínio, uma de entrada e uma de saída, a interface de entrada será a porta por onde o mundo externo irá interagir com a camada de domínio e a interface de saída será a porta por onde a camada de domínio interagirá com o mundo externo:</p><p>No contexto do Angular, podemos aproveitar o seu poderoso sistema de injeção de dependências combinado com <strong>InjectionTokens:</strong></p><h4>Produtos</h4><p>Porta de entrada</p><pre>import { InjectionToken } from &quot;@angular/core&quot;;<br>import { Product } from &quot;@application/domain/entities/product&quot;;<br>import { Observable } from &#39;rxjs&#39;;<br><br>export interface ProductList {<br>    total: number<br>    skip: number<br>    limit: number<br>    products: Product[]<br>}<br><br>export interface IproductInputPort {<br>    getProducts(): Observable&lt;ProductList&gt;;<br>    nextPage(): void;<br>    prevPage(): void;<br>}<br><br>export const PRODUCT_INPUT_PORT = new InjectionToken&lt;IproductInputPort&gt;(&#39;PRODUCT_INPUT_PORT&#39;)</pre><p>Porta de saída</p><pre>import { InjectionToken } from &quot;@angular/core&quot;;<br>import { Observable } from &quot;rxjs&quot;;<br>import { ProductList } from &quot;./input.port&quot;;<br><br>export interface IproductOutputPort {<br>    getProducts(limit: number, skip: number): Observable&lt;ProductList&gt;;<br>}<br><br>export const PRODUCT_OUTPUT_PORT = new InjectionToken&lt;IproductOutputPort&gt;(&#39;PRODUCT_OUTPUT_PORT&#39;)</pre><h4>Carrinho</h4><p>Porta de entrada</p><pre>import { InjectionToken } from &quot;@angular/core&quot;<br>import { CartProduct } from &quot;@application/domain/entities/cart&quot;<br>import { Product } from &quot;@application/domain/entities/product&quot;<br>import { Observable } from &quot;rxjs&quot;<br><br>export interface ICartInputPort {<br>    addToCart(cart: Product): Observable&lt;CartProduct | null&gt;<br>}<br><br>export const CART_INPUT_PORT = new InjectionToken&lt;ICartInputPort&gt;(&#39;CART_INPUT_PORT&#39;)</pre><p>Porta de saída</p><pre>import { InjectionToken } from &quot;@angular/core&quot;<br>import { Cart, CartProduct } from &quot;@application/domain/entities/cart&quot;<br>import { Product } from &quot;@application/domain/entities/product&quot;<br>import { Observable } from &quot;rxjs&quot;<br><br>export interface ICartOutputPort {<br>    persistCart(cart: Cart): Observable&lt;Cart | null&gt;<br>    getPersistedCart(): Observable&lt;Cart | null&gt;<br>    getCartItem(productId: number): Observable&lt;CartProduct | null&gt;<br>    getCartItemFromServer(productId: number): Observable&lt;Product | null&gt;<br>}<br>export const CART_OUTPUT_PORT = new InjectionToken&lt;ICartOutputPort&gt;(&#39;CART_OUTPUT_PORT&#39;)</pre><h3>Adapters</h3><p>Agora vamos definir nossos adaptadores, que vão ser as implementações concretas das nossas portas de entrada e saída para a camada de aplicação.</p><p>Eles podem ser serviços comuns do Angular.</p><h4>Produtos</h4><p>Adaptador de entrada</p><pre>import { Injectable, Provider, inject } from &quot;@angular/core&quot;;<br>import { Product } from &quot;@application/domain/entities/product&quot;;<br>import { AddToCartService } from &quot;@application/domain/use_cases/AddToCart&quot;;<br>import { CART_INPUT_PORT, ICartInputPort } from &quot;@application/ports/cart/input&quot;;<br>import { CART_OUTPUT_PORT } from &quot;@application/ports/cart/output&quot;;<br><br>@Injectable({ providedIn: &#39;root&#39; })<br>export class CartInputAdapter implements ICartInputPort {<br><br>    private cartOutPort = inject(CART_OUTPUT_PORT);<br>    private cartApplication = new AddToCartService(this.cartOutPort);<br><br>    addToCart(product: Product) {<br>        const productAdded = this.cartApplication.addToCart(product);<br>        return productAdded;<br>    }<br>}<br><br>export function provideCartInputAdapter(): Provider {<br>    return {<br>        provide: CART_INPUT_PORT,<br>        useClass: CartInputAdapter,<br>    }<br>}</pre><p>Adaptador de saída</p><pre>import { HttpClient } from &quot;@angular/common/http&quot;;<br>import { Injectable, inject } from &quot;@angular/core&quot;;<br>import { Cart } from &quot;@application/domain/entities/cart&quot;;<br>import { Product } from &quot;@application/domain/entities/product&quot;;<br>import { ICartOutputPort } from &quot;@application/ports/cart/output&quot;;<br>import { API_URL } from &quot;@resources/api&quot;;<br>import { of } from &quot;rxjs&quot;;<br><br>@Injectable({ providedIn: &#39;root&#39; })<br>export class CartOutputAdapter implements ICartOutputPort {<br>    private httpClient = inject(HttpClient)<br>    private apiEndpoint = inject(API_URL)<br><br>    persistCart(cart: Cart) {<br>        sessionStorage.setItem(&#39;cart&#39;, JSON.stringify(cart));<br><br>        const stored = sessionStorage.getItem(&#39;cart&#39;);<br><br>        if (!stored) return of(null);<br><br>        return of(JSON.parse(stored) as Cart);<br>    }<br><br>    getPersistedCart() {<br>        const cart = sessionStorage.getItem(&#39;cart&#39;);<br><br>        if (!cart) return of(null);<br><br>        const parsed = JSON.parse(cart) as Cart;<br><br>        return of(parsed);<br>    }<br><br>    getProductFromCart(productId: number) {<br>        const cart = sessionStorage.getItem(&#39;cart&#39;);<br><br>        if (!cart) return of(null);<br><br>        const product = (JSON.parse(cart) as Cart).getCartProduct(productId);<br><br>        return of(product)<br>    }<br><br>    getProductFromServer(productId: number) {<br>        return this.httpClient.get&lt;Product | null&gt;(`${this.apiEndpoint}/${productId}`);<br>    }<br>}<br><br></pre><h4>Carrinho</h4><p>Adaptador de entrada</p><pre>import { Injectable, Provider, inject } from &quot;@angular/core&quot;;<br>import { Product } from &quot;@application/domain/entities/product&quot;;<br>import { AddToCartService } from &quot;@application/domain/use_cases/AddToCart&quot;;<br>import { CART_INPUT_PORT, ICartInputPort } from &quot;@application/ports/cart/input&quot;;<br>import { CART_OUTPUT_PORT } from &quot;@application/ports/cart/output&quot;;<br><br>@Injectable({ providedIn: &#39;root&#39; })<br>export class CartInputAdapter implements ICartInputPort {<br><br>    private cartOutPort = inject(CART_OUTPUT_PORT);<br>    private cartApplication = new AddToCartService(this.cartOutPort);<br><br>    addToCart(product: Product) {<br>        const productAdded = this.cartApplication.addToCart(product);<br>        return productAdded;<br>    }<br>}<br><br>export function provideCartInputAdapter(): Provider {<br>    return {<br>        provide: CART_INPUT_PORT,<br>        useClass: CartInputAdapter,<br>    }<br>}</pre><p>Adaptador de saída</p><pre>import { HttpClient } from &quot;@angular/common/http&quot;;<br>import { Injectable, inject } from &quot;@angular/core&quot;;<br>import { Cart } from &quot;@application/domain/entities/cart&quot;;<br>import { Product } from &quot;@application/domain/entities/product&quot;;<br>import { ICartOutputPort } from &quot;@application/ports/cart/output&quot;;<br>import { API_URL } from &quot;@resources/api&quot;;<br>import { of } from &quot;rxjs&quot;;<br><br>@Injectable({ providedIn: &#39;root&#39; })<br>export class CartOutputAdapter implements ICartOutputPort {<br>    private httpClient = inject(HttpClient)<br>    private apiEndpoint = inject(API_URL)<br><br>    persistCart(cart: Cart) {<br>        sessionStorage.setItem(&#39;cart&#39;, JSON.stringify(cart));<br><br>        const stored = sessionStorage.getItem(&#39;cart&#39;);<br><br>        if (!stored) return of(null);<br><br>        return of(JSON.parse(stored) as Cart);<br>    }<br><br>    getPersistedCart() {<br>        const cart = sessionStorage.getItem(&#39;cart&#39;);<br><br>        if (!cart) return of(null);<br><br>        const parsed = JSON.parse(cart) as Cart;<br><br>        return of(parsed);<br>    }<br><br>    getCartItem(productId: number) {<br>        const cart = sessionStorage.getItem(&#39;cart&#39;);<br><br>        if (!cart) return of(null);<br><br>        const product = (JSON.parse(cart) as Cart).getCartProduct(productId);<br><br>        return of(product)<br>    }<br><br>    getCartItemFromServer(productId: number) {<br>        return this.httpClient.get&lt;Product | null&gt;(`${this.apiEndpoint}/${productId}`);<br>    }<br>}<br><br></pre><p>Se quisermos garantir ainda mais flexibilidade também podemos defini-los como sem o uso do decorator @Injectable e usar os providers useValue ou useFactory do Angular para prover uma instancia dessa classe como dependência quando solicitada.</p><p>Além disso, perceba que criamos portas de entrada e saída com interfaces distintas, porém, dependendo do cenário, poderíamos usar a mesma para ambas e diminuir a quantidade de código. No entanto, a ideia aqui é mostrar a flexibilidade que segregar essas interfaces pode trazer.</p><p>E e a propósito, você deve ter percebido que a interface ICartOutputPort possui mais assinaturas de métodos, dependendo do tamanho da interface, é interessante até mesmo segregar em portas menores e mais específicas</p><pre>export interface ICartOutputPort {<br>    persistCart(cart: Cart): Observable&lt;Cart | null&gt;<br>    getPersistedCart(): Observable&lt;Cart | null&gt;<br>    getCartItem(productId: number): Observable&lt;CartProduct | null&gt;<br>    getCartItemFromServer(productId: number): Observable&lt;Product | null&gt;<br>}</pre><h3>Drivers e Driven Actors</h3><h4>Drivers</h4><p>No contexto da nossa aplicação, os drivers serão componentes, diretivas, pipes e outros serviços, ou seja, qualquer ator que precisar interagir com a camada de aplicação mais interna.</p><p>Como o componente e a diretiva a seguir:</p><pre>import { CommonModule } from &quot;@angular/common&quot;;<br>import { Component, inject } from &quot;@angular/core&quot;;<br>import { PRODUCT_INPUT_PORT } from &quot;@application/ports/products/input.port&quot;;<br>import { AddToCartDirective } from &quot;@drivers/directives/addToCart.directive&quot;;<br>import { provideCartAdapters } from &quot;@resources/adapters/cart/cart.providers&quot;;<br>import { provideProductsAdapters } from &quot;@resources/adapters/products/products.providers&quot;;<br>import { provideApi } from &quot;@resources/api&quot;;<br><br>@Component({<br>    selector: &#39;app-products&#39;,<br>    templateUrl: &#39;./products.component.html&#39;,<br>    styleUrl: &#39;./products.component.scss&#39;,<br>    standalone: true,<br>    imports: [AddToCartDirective, CommonModule],<br>    providers: [<br>        provideApi(&#39;product&#39;),<br>        ...provideCartAdapters(),<br>        ...provideProductsAdapters(),<br>    ],<br>    template: `<br>    &lt;div class=&quot;container&quot;&gt;<br>    &lt;h1&gt;Products&lt;/h1&gt;<br>    &lt;h4&gt;Choice your product today!&lt;/h4&gt;<br><br>    &lt;div class=&quot;listControl&quot;&gt;<br>        &lt;button (click)=&quot;productService.prevPage()&quot;<br>            class=&quot;prev&quot;&gt;Previous page&lt;/button&gt;<br>        &lt;button (click)=&quot;productService.nextPage()&quot;<br>            class=&quot;next&quot;&gt;Next page&lt;/button&gt;<br>    &lt;/div&gt;<br><br>    &lt;ul&gt;<br>        @for (item of (products | async)?.products; track item.id) {<br>        &lt;li [attr.data-id]=&quot;item.id&quot;&gt;<br>            &lt;div&gt;<br>                &lt;img [src]=&quot;item.thumbnail&quot;<br>                    [alt]=&quot;item.description&quot;&gt;<br>                &lt;div&gt;<br>                    &lt;h2&gt;{{item.title}}&lt;/h2&gt;<br>                    &lt;p&gt;{{item.description}}&lt;/p&gt;<br>                    &lt;h1&gt;{{item.price | currency }}&lt;/h1&gt;<br>                &lt;/div&gt;<br>                &lt;button class=&quot;addToCart&quot;<br>                    addToCart<br>                    [product]=&quot;item&quot;&gt;Add to Cart&lt;/button&gt;<br>            &lt;/div&gt;<br>        &lt;/li&gt;}@empty {<br>        &lt;div class=&quot;empty&quot;&gt;Nada por aqui!&lt;/div&gt;<br>        }<br>    &lt;/ul&gt;<br>&lt;/div&gt;<br>    `,<br>})<br>export class HomeProductComponent {<br>    productService = inject(PRODUCT_INPUT_PORT);<br>    products = this.productService.getProducts();   <br>}</pre><p>Aqui o nosso componente faz uso do conceito de inversão de dependência, ele não ideia de quem é esse serviço de produto, estamos dizendo a ele quais são através dos providers:</p><pre>import { Provider } from &quot;@angular/core&quot;;<br>import { PRODUCT_INPUT_PORT } from &quot;@application/ports/products/input.port&quot;;<br>import { PRODUCT_OUTPUT_PORT } from &quot;@application/ports/products/output.port&quot;;<br>import { API_URL } from &quot;@resources/api&quot;;<br>import { ProductsInputAdapter } from &quot;./products.input.adapter&quot;;<br>import { ProductsOutPutAdapter } from &quot;./products.output.adapter&quot;;<br><br>export function provideProductsInputAdapter(): Provider {<br>    return {<br>        provide: PRODUCT_INPUT_PORT,<br>        useClass: ProductsInputAdapter,<br>    }<br>}<br><br>export function provideProductsOutputAdapter(): Provider {<br>    return {<br>        provide: PRODUCT_OUTPUT_PORT,<br>        useClass: ProductsOutPutAdapter,<br>        deps: [API_URL]<br>    }<br>}<br><br><br>export function provideProductsAdapters(): Provider[] {<br>    return [<br>        provideProductsInputAdapter(),<br>        provideProductsOutputAdapter(),<br>    ];<br>}</pre><p>O mesmo se aplica para a diretiva <strong>AddToCartDirective, </strong>que encapsula funcionalidade de adicionar um produto ao carrinho de compras<strong>:</strong></p><pre>import { DestroyRef, Directive, inject, input } from &quot;@angular/core&quot;;<br>import { takeUntilDestroyed } from &quot;@angular/core/rxjs-interop&quot;;<br>import { Product } from &quot;@application/domain/entities/product&quot;;<br>import { CART_INPUT_PORT } from &quot;@application/ports/cart/input&quot;;<br><br>@Directive({<br>    selector: &#39;[addToCart]&#39;,<br>    standalone: true,<br>    host: {<br>        &#39;(click)&#39;: &#39;add()&#39;<br>    }<br>})<br>export class AddToCartDirective {<br>    cartService = inject(CART_INPUT_PORT);<br>    product = input&lt;Product&gt;({} as Product);<br>    destroyRef = inject(DestroyRef);<br><br>    add() {<br>        this.cartService.addToCart(this.product()).pipe(takeUntilDestroyed(this.destroyRef)).subscribe()<br>    }<br>}</pre><h4>Driven Actors</h4><p>Já os Driven Actors, será qualquer recurso externo a nossa aplicação, por exemplo, o serviço CartOutputAdapter interage com a sessionStorage do navegador, o serviço ProductOutputAdapter interage com uma API externa para buscar os produtos.</p><p>Esses são atores com quem a camada de aplicação irá interagir, até mesmo o gerenciamento de estado da aplicação poderia ser um Driven Actor, o benefício disso a possiblidade de fazer modificações nessa camada sem afetar o funcionamento da aplicação, desde que as interfaces das portas sejam respeitadas.</p><h4>Aplicação</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Of6Osxg6xpXS3Fhem7nFfg.png" /></figure><h3>Conclusão</h3><p>Nesse post procuramos explorar o conceito da Arquitetura Hexagonal no contexto do Angular, percebe-se que é perfeitamente possível aplicá-lo, aproveitando os benefícios de desacoplamento, facilidade de manutenção e principalmente (o que eu gostaria de mostrar numa próxima oportunidade, inclusive) os testes, já que podemos trocar facilmente as dependências por mocks, stubs e etc. Além de isolarmos as regras da nossa aplicação, tornando elas mais agnósticas ao framework e bibliotecas.</p><p>No entanto, é importante observar que utilizar essa abordagem traz uma grau de complexidade para a aplicação, um que talvez a sua aplicação não precise. O Angular é um framework poderoso, muitos dos conceitos que aproveitamos aqui já são nativos dele, como os InjectionsTokens e os providers com inversão de dependências.</p><p>No fim, essa abordagem pode ser ótima na medida que você entender a maturidade da sua aplicação, além de analisar se os conceitos de “Ports and Adapters” vai mais ajudar do que atrapalhar o seu projeto. A maturidade e conhecimento do time também é importante, analise os trade-offs com sabedoria.</p><p>Espero que tenham gostado, até a próxima!</p><p>Link do projeto no github: <a href="https://github.com/danilolmc/hexagonal-arch-angular">https://github.com/danilolmc/hexagonal-arch-angular</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d589688f9950" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Deployando um Microfrontend Angular na AWS com Github Actions]]></title>
            <link>https://danlima-dev.medium.com/deployando-um-microfrontend-angular-na-aws-com-github-actions-d1da4180cac5?source=rss-c263b528da4e------2</link>
            <guid isPermaLink="false">https://medium.com/p/d1da4180cac5</guid>
            <category><![CDATA[deployment]]></category>
            <category><![CDATA[module-federation]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[angular]]></category>
            <category><![CDATA[webpack]]></category>
            <dc:creator><![CDATA[Danilo Lima]]></dc:creator>
            <pubDate>Sat, 27 Apr 2024 13:31:27 GMT</pubDate>
            <atom:updated>2024-05-29T21:31:18.291Z</atom:updated>
            <content:encoded><![CDATA[<p>Neste post vamos aprender a implantar uma aplicação Microfrontend na AWS, usando Nx para gerenciamento de monorepo, um pipeline de CI/CD com Gtihub Actions, e de quebra aprender sobre Infraestrutura como Código com CloudFormation, vamos nessa!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/940/1*AB16rT6W82RmDBcuFb_3nA.png" /></figure><p>Vamos dividir esse objetivo em algumas tarefas:</p><ul><li>Vamos configurar o repositório da nossa aplicação e criar um pipeline com o Github Actions</li><li>Além disso, vamos configurar um provedor de identidade usando AWS IAM para uso no nosso pipeline, caso não tenha uma conta você pode aproveitar o nível gratuito criando uma conta por <a href="https://aws.amazon.com/pt/free/?gclid=CjwKCAjwoa2xBhACEiwA1sb1BJCAWFWYZLOJHu4c7K28zGdvjTb2-dsndA8-z5nzaG3v7UeHlzHoMBoCXCYQAvD_BwE&amp;trk=9eeea834-765c-4895-95ec-d2fb1a1a573d&amp;sc_channel=ps&amp;ef_id=CjwKCAjwoa2xBhACEiwA1sb1BJCAWFWYZLOJHu4c7K28zGdvjTb2-dsndA8-z5nzaG3v7UeHlzHoMBoCXCYQAvD_BwE:G:s&amp;s_kwcid=AL!4422!3!561843094998!p!!g!!amazon%20aws!15278604641!130587773020&amp;all-free-tier.sort-by=item.additionalFields.SortRank&amp;all-free-tier.sort-order=asc&amp;awsf.Free%20Tier%20Types=*all&amp;awsf.Free%20Tier%20Categories=*all">aqui</a>;</li><li>Também vamos usar o CloudFormation e o S3 para automatizar a implantação e orquestração da nossa aplicação microfrontend.</li></ul><p>O código da final do projeto que vamos deployar na AWS pode ser encontrado nesse <a href="https://github.com/danilolmc/ecommerce-mfe">repositório</a>.</p><h3>Overview da a aplicação</h3><p>Nossa aplicação se trata de um e-commerce com funcionalidades de listagem de produtos e carrinho, que é composta por uma aplicação shell e dois microfrontends, e usa a estratégia de integração por rotas:</p><ul><li>apps/shopping-shell: é a aplicação container, responsável por orquestrar os dois microfrontends, ela que decide qual aplicação que será carregada baseado nas rotas;</li><li>apps/cart: é a aplicação responsável por gerenciar o carrinho de compras do usuário, com funcionalidades de adicionar mais itens e removê-los, calcular subtotal da compra e esvaziar carrinho;</li><li>apps/shop: aplicação principal, responsável por listar os produtos partir desta fake <a href="https://dummyjson.com/docs/products">API</a> gratuita, além de permitir que os produtos sejam adicionados ao carrinho.</li></ul><p>Além disso, nossa aplicação faz uso de duas bibliotecas compartilhadas existentes no nosso monorepo:</p><ul><li>libs/shared/api: biblioteca responsável por abstrair a comunicação com a <a href="https://dummyjson.com/docs/products">API de produtos</a>;</li><li>libs/shared/state: biblioteca responsável por gerenciar o estado global da nossa aplicação.</li></ul><h3>Criando os scripts de deploy</h3><p>Agora, vamos criar os scripts de implantação para cada uma das nossas aplicações, eles nos ajudarão a separar como e onde cada aplicação será implantada na AWS. A criação do nossos scripts envolverá dois passos:</p><ol><li>Criar um shell script reutilizável entre as nossas aplicações, ele será responsável por se comunicar com o CloudFormation e o S3</li><li>Em cada aplicação dentro do monorepo vamos configurar um script que será responsável por chamar o shell script reutilizável passando os parâmetro necessários para implantar cada aplicação.</li></ol><p>Para visualizar o código final e ver a estrutura da aplicação clone o repositório nesse <a href="https://github.com/danilolmc/ecommerce-mfe">link</a> e siga as instruções do README do projeto para instalação das dependências.</p><h4>Criando o script reutilizável</h4><p>Na raiz do nosso projeto criamos o diretório scripts/deploy.sh, com a estrutura explicada em detalhes abaixo:</p><pre>#!/bin/bash<br><br># definimos os parâmetros esperados pelo script para provisionar <br># os recursos e implantar o microfrontend<br># URL do template do CloudFormation<br>TEMPLATE_URL=$1<br># Nome do bucket do microfrontend<br>BUCKET_NAME=$2<br># Nome do microfrontend<br>APP_NAME=$3<br># Nome da stack do CloudFormation<br>STACK_NAME=$4<br># URL do app shell implantad (nesse contexto será uma URL HTTP no padrão do S3)<br>SHELL_APP=$5<br># verificamos se o bucket não existe<br>if ! aws s3api head-bucket --bucket $BUCKET_NAME; then<br>    echo &quot;Validating Template...&quot;<br>    <br>    # valida o template do cloudformation antes de fazer o deploy dele<br>    if aws cloudformation validate-template --template-body file://$TEMPLATE_URL; then <br>        echo;<br>        echo &quot;Template validate successfully&quot;<br>        echo;<br>        echo &quot;Bucket $BUCKET_NAME does not exist.&quot;<br>        echo;<br>        echo &quot;Creating bucket $BUCKET_NAME...&quot;<br>        # se o template for válido, faz o deploy dele para provisionar <br>        # o bucket do microfrontend<br>        if aws cloudformation deploy \<br>            --template-file &quot;$TEMPLATE_URL&quot; \<br>            --stack-name &quot;$STACK_NAME&quot; \<br>            --parameter-overrides BucketName=$BUCKET_NAME ShellAppBucketUrl=$SHELL_APP \<br>            --capabilities &quot;CAPABILITY_IAM&quot;<br>        then<br>            echo &quot;Stack created successfully&quot;<br>        else<br>            echo &quot;Failed to create stack&quot;<br>            exit 1<br>        fi<br>    else <br>        echo &quot;Invalid template&quot;<br>        exit 1<br>    fi<br>fi<br><br>echo Deploying $APP_NAME app...<br># se tudo correr bem com o provisionamento dos recursos, movemos os arquivos<br># do microfrontend para o bucket S3 que acabou de ser criado pela stack <br># do CloudFormation<br>if ! aws s3 sync dist/apps/$APP_NAME s3://$BUCKET_NAME --delete; then<br>    echo Error on move $APP_NAME app files to S3<br>    exit 1;<br>fi<br>echo;<br>echo $APP_NAME app deployed successfully</pre><p>Com esse script temos uma sequência de passos padrão que será reaproveitada por qualquer microfrontend no nosso monorepo, o próximo passo será configurar nossos MFE’s para que façam uso desse script.</p><h4>Definindo os scripts de cada aplicação</h4><p>O Nx nos permite adicionar scripts customizados para cada uma das nossas aplicações dentro do monorepo, modificando o arquivo project.json vamos criar um script de deploy específico a cada aplicação.</p><p>Esse script poderá ser executado através dos comandos padrões do Nx, como:</p><p>nx affected -t=deploy e nx run shop:deploy</p><p>Vamos exemplificar a definindo o script do microfrontend <strong>shop</strong>…</p><pre>&quot;deploy&quot;: {<br>  &quot;builder&quot;: &quot;nx:run-commands&quot;,<br>  &quot;command&quot;: &quot;bash -c &#39;chmod +x ./scripts/deploy.sh; ./scripts/deploy.sh apps/shop/template.yaml $S3_BUCKET_APP_SHOP shop shop-app-stack $APP_SHELL_URL&#39;&quot;,<br>  &quot;parallel&quot;: false<br>}</pre><p>Observe que esse script de deploy chama nosso script reutilizável passando alguns parâmetros:</p><ul><li>apps/shop/template.yaml: o diretório do template do CloudFormation respectivo a aplicação shop;</li><li>$S3_BUCKET_APP_SHOP: é uma referência a uma variável de ambiente, nela precisaremos definir o nome do bucket S3 onde será armazenado os arquivos desse microfrontend depois de compilados pelo Web, definiremos como <strong>app-shop</strong>.</li><li>shop: é apenas o nome da aplicação a ser usado pelo script reutilizável para identificar melhor qual aplicação está sendo implantada.</li><li>shop-app-stack: nome dado a stack do CloudFormation da aplicação;</li><li>$APP_SHELL_URL: outra referência á uma variável de ambiente, nela precisaremos definir a url http do bucket S3 onde ficará armazenado a aplicação shell, que será <br><strong>https://app-shopping-shell.s3.amazonaws.com.</strong></li></ul><p>Essa configuração será idêntica para as outras duas aplicações, shopping-shell e cart. O que irá mudar é apenas os parâmetros passados, dê uma olhada no <a href="https://github.com/danilolmc/ecommerce-mfe">repositório</a> do projeto com o código final para visualizar como está configurado para os outros dois microfrontends.</p><h3>Preparando o ambiente</h3><h4>Configurando o pipeline do GitHub Actions</h4><p>Vamos entender nosso workflow que automatizará a implantação das nossas aplicações na AWS.</p><p>Precisamos criar um workflow no diretório .github/workflows/deploy.yaml com a seguinte estrutura:</p><pre>name: Build and Deploy to Amazon S3<br>on:<br>  push:<br>    branches:<br>      - main<br>jobs:<br>  build_and_deploy: <br>    runs-on: ubuntu-latest<br>    # Faremos uso de OICD para permitir<br>    # que nosso pipeline gerencie recursos da AWS sem precisar de<br>    # pares de chaves<br>    permissions:<br>      contents: read<br>      id-token: write<br>    env:<br>      APP_SHELL_URL: ${{ secrets.APP_SHELL_URL }}<br>      APP_SHOP_URL: ${{ secrets.APP_SHOP_URL }}<br>      APP_CART_URL: ${{ secrets.APP_CART_URL }}<br>      S3_BUCKET_APP_SHELL: ${{ secrets.S3_BUCKET_APP_SHELL }}<br>      S3_BUCKET_APP_SHOP: ${{ secrets.S3_BUCKET_APP_SHOP }}<br>      S3_BUCKET_APP_CART: ${{ secrets.S3_BUCKET_APP_CART }}<br>    steps:<br>      - name: Checkout Code<br>        uses: actions/checkout@v3<br>        with:<br>          fetch-depth: 0<br>      - name: Set up Node.js<br>        uses: actions/setup-node@v3<br>        with:<br>          node-version: &#39;20&#39;<br>      <br>      # esse passo vai nos ajudar a definir qual o último commit e branch base<br>      # para que o comando nx affected possa identificar os projetos afetados <br>      # pela mudança do commit<br>      - name: Set base and head SHAs<br>        uses: nrwl/nx-set-shas@v3<br>      - name: Install dependencies<br>        run: npm ci<br>      # buildando somente os projetos e bibliotecas que foram modificados<br>      - name: Build Affected Projects<br>        run: npx nx affected --target=build --base=$NX_BASE --head=$NX_HEAD --configuration=production<br>      - name: Run Tests for Affected Projects<br>        run: npm run test:all<br>      # aqui nosso pipeline assume temporariamente as permissões necessárias<br>      # para se comunicar com a AWS<br>      - name: Configure AWS credentials<br>        uses: aws-actions/configure-aws-credentials@v4<br>        with:<br>          aws-region: ${{ secrets.AWS_REGION }}<br>          role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}<br>          role-session-name: ${{ secrets.AWS_ROLE_SESSION_NAME }}<br>      # e aqui enfim, deployamos as aplicações/bibliotecas afetadas <br>      # pelo commit atual<br>      - name: Deploy Affected Projects to S3<br>        run: npx nx affected --target=deploy --base=$NX_BASE --head=$NX_HEAD</pre><h4>Configurando o provedor de identidade no IAM</h4><p>Apesar de ser possível fazer o uso de secret access keys no nosso workflow, vamos configurar um provedor de identidade com o AWS IAM para que nosso pipeline assuma permissões temporariamente, suficientes para prover os recursos através das stacks do CloudFormation e para mover os arquivos compilados para os respectivos buckets no S3.</p><p>Para configurá-lo, siga esse <a href="https://medium.com/israeli-tech-radar/openid-connect-and-github-actions-to-authenticate-with-amazon-web-services-9a66b3b88e92">artigo</a>.</p><p>Para fins de teste, no passo de adicionar permissões ao provedor de identidade indicado no artigo, adicione as permissões AmazonS3FullAccess e AWSCloudFormationFullAccess.</p><blockquote>Num cenário real de implantação você deve configurar essas permissões seguindo o princípio do menor privilégio.</blockquote><h4>Definindo as variáveis de ambiente</h4><p>Nosso projeto utiliza algumas variáveis de ambiente, precisamos defini-las como secrets do repositório.</p><p>Para isso, com o repositório criado, navegue até Settings &gt; Security &gt;Actions e crie os secrets de acordo com os exemplos abaixo:</p><pre>APP_CART_URL=https://app-cart.s3.amazonaws.com,<br>APP_SHOP_URL=https://app-shop.s3.amazonaws.com<br>APP_SHELL_URL=https://app-shopping-shell.s3.amazonaws.com<br>AWS_REGION=us-east-1<br>AWS_ROLE_SESSION_NAME=github_workflow<br># Aqui você substituirá o valor da variável pelo ARN da role que foi <br># criado anteriormente, quando configuramos o provedor de identidade no IAM<br>AWS_ROLE_TO_ASSUME=arn:aws:iam::123456789012:role/NomeDaSuaRole<br>S3_BUCKET_APP_CART=app-cart<br>S3_BUCKET_APP_SHELL=app-shopping-shell<br>S3_BUCKET_APP_SHOP=app-shop</pre><h3>Implantando a aplicação</h3><p>Por fim, só precisamos fazer o push do projeto para branch main do nosso repositório, se tudo estiver configurado corretamente a nossa aplicação deve ser implantada com sucesso.</p><p>E então você verá os buckets criados na sua conta AWS.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/764/1*xOfIueF5YgOlvKz34P-aNA.png" /><figcaption>Bukets S3 das três aplicações microfrontend</figcaption></figure><p>Bem como as stacks que os criaram executadas pelo CloudFormation.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/775/1*FTnzUnsYrjOPF9O2fPm9kw.png" /><figcaption>Stacks do CloudFormation executadas com sucesso</figcaption></figure><p>Assim, temos um fluxo de implantação totalmente automatizado, que realizará o deploy somente das aplicações que forem afetadas pelas alterações contidas no commit mais recente.</p><p>E para acessar nossa aplicação você pode usar o endereço do objeto index.html armazenado no bucket S3 app-shopping-shell!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/427/1*ZhTNTX7XMDAw2ia326fWNw.png" /><figcaption>URL do bucket S3 da aplicação shell</figcaption></figure><p>E então interagir com a aplicação!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/426/1*_EReOp_Jt7qSfbuVo5C4kg.gif" /><figcaption>Vídeo de interação com a aplicação</figcaption></figure><h3>Conclusão</h3><p>Neste artigo, exploramos como implementar uma aplicação microfrontend utilizando o Nx para gerenciamento de monorepos e a estratégia de Module Federation do Webpack. Além disso, detalhamos como usar o GitHub Actions para automatizar o provisionamento de recursos por meio da Infraestrutura como Código, utilizando o CloudFormation.</p><p>É importante lembrar que há outras estratégias para a aplicação shell orquestrar os microfrontends, como a recuperação via requisição HTTP. Também é possível configurar uma distribuição com o CloudFront para evitar o uso direto das URLs do Bucket ao acessar a aplicação.</p><p>Mas esses são tópicos que podemos explorar em detalhes em um próximo artigo.</p><p>Fique a vontade para dar uma olhada no código final do projeto, segue o link do repositório: <a href="https://github.com/danilolmc/ecommerce-mfe">https://github.com/danilolmc/ecommerce-mfe</a></p><p>Enfim, é isso, espero que tenha curtido o artigo!</p><p>Até a próxima! 😉</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d1da4180cac5" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Angular, Signals e Web Components]]></title>
            <link>https://danlima-dev.medium.com/angular-signals-e-web-components-d51a6a02819f?source=rss-c263b528da4e------2</link>
            <guid isPermaLink="false">https://medium.com/p/d51a6a02819f</guid>
            <category><![CDATA[reactive-programming]]></category>
            <category><![CDATA[web-components]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[angular]]></category>
            <dc:creator><![CDATA[Danilo Lima]]></dc:creator>
            <pubDate>Fri, 05 Apr 2024 21:02:00 GMT</pubDate>
            <atom:updated>2024-04-08T17:15:39.264Z</atom:updated>
            <content:encoded><![CDATA[<p>Nesse post vamos entender como integrar web components dentro da nossa aplicação Angular!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/940/1*I8JM2Ky8v_XwahaOJUHUQg.png" /></figure><p>O mundo do desenvolvimento Front-end Web é marcado pela diversidade de ferramentas para a construção de aplicações baseadas em componentes. Essa variedade introduz vários desafios, especialmente porque cada ferramenta apresenta suas próprias abordagens em relação a conceitos fundamentais como componentes, gerenciamento de estado e etc.</p><p>Além disso, o ecossistema único de cada uma dessas ferramentas complica a reutilização do código desenvolvido, criando barreiras no aproveitamento de soluções entre projetos.</p><p>No entanto, felizmente, existe uma estratégia para tornar o desenvolvimento de aplicações web mais agnóstico em termos de ferramentas e favorecer o reaproveitamento de código. Essa solução é conhecida como Web Components.</p><p>De acordo com o site MDN, Web Components são…”</p><blockquote>um conjunto de diferentes tecnologias que permitem criar elementos personalizados reutilizáveis — com sua funcionalidade encapsulada, separada do resto do seu código — e utilizá-los em suas aplicações web.</blockquote><p>Ou seja, podemos utilizar web components para encapsular nosso código JavaScript na forma de componentes e reutilizá-los entre nossas aplicações!!</p><p>Nesse contexto, vamos entender como faríamos isso com o Angular!</p><h3>Usando Web components no Angular</h3><p>Vamos dividir esse objetivo em dois passos:</p><ol><li>Vamos criar um componente <strong>switch </strong>usando Web Components;</li><li>Integrar o web component na nossa aplicação Angular.</li></ol><h4>Criando o web component</h4><p>Em um arquivo switch.component.js vamos declarar o seguinte código:</p><pre>class SwitchComponent extends HTMLElement {<br>    <br>    constructor() {<br>        super();<br><br>        this.attachShadow({ mode: &#39;open&#39; });<br><br>        const template = document.createElement(&#39;template&#39;);<br>        template.innerHTML = `<br>            &lt;style&gt;<br>                .switch-element {<br>                    --width: 60px;<br>                    display: flex;<br>                    background: none;<br>                    align-items: baseline;<br>                    padding: 0.15rem;<br>                    position: relative;<br>                    height: 30px;<br>                    cursor: pointer;<br>                    width: var(--width);<br>                    border-radius: 50px;<br>                    border: 2px solid #000;<br>                    box-sizing: border-box;<br>                }<br><br>                .switch-element label {<br>                    width: 22px;<br>                    height: 22px;<br>                    display: inline-block;<br>                    line-height: 20px;<br>                    background-color: #000;<br>                    border-radius: 55px;<br>                    transform: translatex(0);<br>                    transition: 0.2s ease;<br>                    box-sizing: border-box;<br>                }<br>            <br>                :host {<br>                    display: inline-block;<br>                }<br><br>                .switch-element.--open label{<br>                    transform: translatex(calc(var(--width) * 0.5));<br>                }<br>            &lt;/style&gt;<br>            &lt;button class=&quot;switch-element&quot; tabindex&gt;<br>                &lt;label&gt;&lt;/label&gt;<br>            &lt;/button&gt;<br>        `;<br><br>        this.shadowRoot.appendChild(template.content.cloneNode(true));<br>        this.switchElement = this.shadowRoot.querySelector(&#39;.switch-element&#39;);<br>    }<br><br>    static get observedAttributes() {<br>        return [&#39;open&#39;];<br>    }<br><br>    attributeChangedCallback() {<br>        this.hasAttribute(&#39;open&#39;)<br>            ? this.switchElement.classList.add(&#39;--open&#39;)<br>            : this.switchElement.classList.remove(&#39;--open&#39;);<br>            this.#dispatchChangeEvent()<br>    }<br><br>    #dispatchChangeEvent(){<br>        this.dispatchEvent(new CustomEvent(&#39;change&#39;, {<br>            detail: this.hasAttribute(&#39;open&#39;)<br>        }))<br>    }<br><br>    connectedCallback() {<br>        this.addEventListener(&#39;click&#39;, () =&gt; {<br>            !this.hasAttribute(&#39;open&#39;)<br>                ? this.setAttribute(&#39;open&#39;, &#39;&#39;)<br>                : this.removeAttribute(&#39;open&#39;)<br>        })<br>    }<br>}<br>customElements.define(&#39;switch-component&#39;, SwitchComponent);</pre><p>Vamos entender alguns métodos do nosso web component:</p><ul><li><strong>observeAttributes:</strong> vai nos ajudar a observar alterações no atributo <strong>open , </strong>definido no nosso componente hospedeiro: switch-component.</li><li><strong>connectedCalback: </strong>é o método executado quando o nosso web component for adicionado ao DOM.</li><li><strong>attributeChangedCallback: </strong>método executado quando houver alteração no valor das propriedades observadas por <strong>observeAttributes.</strong></li></ul><h4>Integrando nosso web component no Angular</h4><p>Agora que temos nosso web component, é hora de integrá-lo a nossa aplicação, para simplificar vamos importar o componente diretamente do diretório do projeto em tempo build.</p><p>No entanto, também poderíamos importá-lo de outras formas, seja como um pacote npm, uma url num bucket S3 e por aí vai.</p><p>Primeiro vamos criar uma aplicação Angular:</p><pre>ng new webcomponents</pre><p>Depois, dentro do diretório src/app/assets, vamos criar um diretório web_components e nele colocar nosso arquivo switch.component.js, criado anteriormente.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/347/1*QYBaLoPZkypmU7Fpiups1Q.png" /></figure><p>Em outros cenários, é comum que web components estejam hospedados externamente à nossa aplicação. Nesses casos é necessário especificar a localização do web component externo. Isso é feito declarando a propriedade scripts no arquivo angular.json, indicando o caminho até o componente.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/545/1*5Jxzxgm_iogFiBmep7CHZA.png" /></figure><p>E por fim, só precisamos declarar o web component switch-componentno template Angular.</p><pre>import { CUSTOM_ELEMENTS_SCHEMA, Component, computed, signal } from &#39;@angular/core&#39;;<br><br>@Component({<br>  selector: &#39;app-root&#39;,<br>  standalone: true,<br>  schemas: [CUSTOM_ELEMENTS_SCHEMA],<br>  styles: [`<br>    button {<br>      display: block;<br>      margin: 1rem 0;<br>    }<br>  `],<br>  template: `<br>    &lt;switch-component (change)=&quot;log($event)&quot; [attr.open]=&quot;isOpenAttribute()&quot;/&gt;<br>    &lt;button (click)=&quot;toggleSwitchState()&quot;&gt;Atualizar estado&lt;/button&gt;<br>  `,<br>})<br>export class AppComponent {<br><br>  isOpen = signal(false);<br>  isOpenAttribute = computed(() =&gt; this.isOpen() ? &#39;&#39; : null);<br><br>  toggleSwitchState() {<br>    this.isOpen.update(value =&gt; !value)<br>  }<br><br>  log(event: Event){<br>    const data = event as CustomEvent;<br>    console.log(&quot;isActive&quot;, data.detail)<br>  }<br>}</pre><blockquote>Sempre que estamos importando um elemento customizado precisamos definir CUSTOM_ELEMENTS_SCHEMA, caso contrário o Angular lançará o erro <a href="https://angular.io/errors/NG8001">https://angular.io/errors/NG8001</a></blockquote><p>E eis o resultado, clicando no botão mudamos o estado do switch para aberto:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*syYDQ0jVDgsk4K8DXVXXJQ.png" /></figure><p>E clicando novamente, mudamos o estado para fechado:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cpmCy91p33UmXsgcRZIgbQ.png" /></figure><p>Além disso, exibimos o estado atual do web component no console, fazendo o Angular ouvir o disparo de um evento customizado de dentro do switch-component.</p><p>E enfim temos um web component integrado na nossa aplicação Angular, legal né?!</p><h3>Conclusão</h3><p>Os Web Componentes são uma solução bastante interessante quando falamos de reutilização de código e diminuição de retrabalho, um dos casos de uso mais comuns são Microfrontends e bibliotecas de componentes, com destaque para este último.</p><p>Em bibliotecas de componentes, por exemplo, podemos desenvolver um componente apenas uma vez, e independente do framework que estiver utilizando, será possível reutilizá-lo.</p><p>E é isso! Obrigado por ter chegado até aqui, espero que tenha curtido o conteúdo!</p><p>Até a próxima, Dev!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d51a6a02819f" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Angular & Web Workers]]></title>
            <link>https://danlima-dev.medium.com/angular-web-workers-080c266bbf04?source=rss-c263b528da4e------2</link>
            <guid isPermaLink="false">https://medium.com/p/080c266bbf04</guid>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[angular]]></category>
            <category><![CDATA[webworker]]></category>
            <category><![CDATA[typescript]]></category>
            <dc:creator><![CDATA[Danilo Lima]]></dc:creator>
            <pubDate>Fri, 29 Mar 2024 00:23:10 GMT</pubDate>
            <atom:updated>2024-07-24T15:28:55.519Z</atom:updated>
            <content:encoded><![CDATA[<p>Durante muito tempo, uma das principais preocupações quando o assunto envolvia performance de aplicações web era com a quantidade e complexidade do código JavaScript sendo executado no navegador, o JavaScript, sendo uma linguagem single-threaded, exigia mais atenção quanto a esse aspecto.</p><p>Porém, com a especificação do HTML 5 isso mudou um pouco, e nesse contexto vamos explorar a API de Web Workers no cenário do Angular, o intuito é te familiarizar sobre como interagir com a API de Web Workers tanto ao usar puramente JavaScript e também com o framework, vamos nessa!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/940/1*GBvUh-UfKBlDTq0rFohb-A.png" /><figcaption>Angular e Web Workers</figcaption></figure><h3>O que são Web Workers?</h3><p>No contexto web, ele é um termo popular que se refere a API de Web Workers, que começou a ser especificada em 2004 com o HTML 5, e foi lançada oficialmente em 2014, o intuito dessa especificação foi permitir que código JavaScript pudesse ser executado em paralelo a thread principal.</p><p>Isto abriu uma série de possibilidades, pois agora é possível executar tarefas complexas do lado do cliente, em um ambiente que possui seu próprio escopo, sem afetar a experiência do usuário.</p><p>Processamento de dados, Operações I/O assíncronas, Tarefas em segundo plano, Cache, Simulações e Modelagens são apenas algumas das tarefas que podemos executar com um worker, que, se fossem executadas na thread principal do JavaScript, certamente impactariam negativamente a experiência do usuário da sua aplicação.</p><h3>Implementando Web Workers</h3><p>Chega de falar!</p><p>Vamos colocar a mão na massa, a implementação é relativamente simples e nesse exemplo vamos precisar apenas de uma página carregando alguns scripts.</p><h4>Nossa página</h4><p>Vamos criar uma página HTML bem simples…</p><pre>&lt;!DOCTYPE html&gt;<br>&lt;html lang=&quot;en&quot;&gt;<br>&lt;head&gt;<br>    &lt;meta charset=&quot;UTF-8&quot;&gt;<br>    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;<br>    &lt;title&gt;Web Worker - Exemplo&lt;/title&gt;<br>&lt;/head&gt;<br>&lt;body&gt;<br>    &lt;h1 id=&quot;result&quot;&gt;&lt;/h1&gt;<br>&lt;/body&gt;<br>&lt;/html&gt;</pre><h4>Adicionando nosso script</h4><p>Vamos incluir o script que irá se comunicar com nosso worker, perceba que a comunicação entre eles é baseada em eventos, é extremamente simples:</p><pre>&lt;!DOCTYPE html&gt;<br>&lt;html lang=&quot;en&quot;&gt;<br>&lt;head&gt;<br>    &lt;meta charset=&quot;UTF-8&quot;&gt;<br>    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;<br>    &lt;title&gt;Web Worker - Exemplo&lt;/title&gt;<br>    &lt;script&gt;<br>        // Verifica se os Web Workers são suportados pelo seu browser<br>        if (window.Worker) {<br>            // Criamos um novo Web Worker<br>            const myWorker = new Worker(&#39;worker.js&#39;);<br>            <br>            // Enviamos uma mensagem para o Web Worker<br>            myWorker.postMessage(10);<br><br>            // Aqui escutamos por mensagens vindas do Web Worker<br>            myWorker.onmessage = function({ data }) {<br>                const element = document.querySelector(&quot;#result&quot;);<br>                element.innerText = data;<br>            };<br><br>            // Apesar de não ser o foco desse post,<br>            // também podemos escutar por erros no Web Worker<br>            myWorker.onerror = function(error) {<br>                console.error(&#39;error:&#39;, error);<br>            };<br>        } else {<br>            console.log(&#39;Seu navegador não tem suporte para Web Workers.&#39;);<br>        }<br>    &lt;/script&gt;<br>&lt;/head&gt;<br>&lt;body&gt;<br>    &lt;h1 id=&quot;result&quot;&gt;&lt;/h1&gt;<br>&lt;/body&gt;<br>&lt;/html&gt;<br></pre><h4>Adicionando nosso Web Worker</h4><p>Não tem muita novidade, o worker é só um código JavaScript que vamos criar em um arquivo worker.jsno mesmo diretório…</p><pre>function fib(n) {<br>  if(n &lt;= 1){<br>    return n;<br>  }else {<br>    return fib(n-1) + fib(n - 2);<br>  }<br>}<br><br>// Enviamos a mensagem de volta para o script que chamou o worker<br>addEventListener(&#39;message&#39;, ({ data }) =&gt; {<br>  const response = fib(data);<br>  postMessage(response);<br>});</pre><p>Usei uma extensão do VSCODE para servir o arquivo HTML e voilà! Eis o nosso resultado!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/333/1*7bfyeA4mEGhjWlLCZ-kT9A.png" /><figcaption>Resultado web worker rodando com live server</figcaption></figure><p>Apareceu bem rápido, mas você pode passar valores acima de 10 para o worker e perceber que não impacta na performance da página que você está visualizando esperando o resultado, apesar de levar algum tempo para o worker processar.</p><h3>Mas como funciona no Angular?</h3><p>No angular é mamão com açúcar.</p><p>Primeiro, garanta que tenha o Angular CLI instalado e crie um projeto novo:</p><pre>ng new app-worker</pre><p>Com o projeto criado, navegue com o terminal até o diretório do projeto e execute o comando abaixo:</p><pre>ng g web-worker app</pre><p>A CLI do Angular vai alterar algumas coisas no seu projeto para deixá-lo no jeitinho pra você usar os Web Workers no Framework, ele basicamente:</p><ol><li>Altera o arquivo a configuração de build no arquivo angular.json do seu projeto</li><li>Cria um arquivo app.worker.ts com um código Typescript de exemplo, que vamos substituir a seguir;</li><li>Cria um arquivo tsconfig.worker.json com algumas configurações;</li><li>E também inclui um código Typescript chamando o worker a partir do seu arquivo app.component.ts;</li></ol><p>E só precisaremos de dois passos :</p><ol><li>Trazendo o código do nosso worker para arquivo app.worker.ts</li></ol><pre>/// &lt;reference lib=&quot;webworker&quot; /&gt;<br>function fib(n: number) {<br>  if(n &lt;= 1){<br>    return n;<br>  }else {<br>    return fib(n-1) + fib(n - 2);<br>  }<br>}<br>addEventListener(&#39;message&#39;, ({ data }) =&gt; {<br>  const response = fib(data);<br>  postMessage(response);<br>});</pre><p>2. Ajustando a chamada ao nosso worker ao estilo Angular</p><pre>import { Component, OnInit, signal } from &#39;@angular/core&#39;;<br>import { FormsModule } from &#39;@angular/forms&#39;;<br><br>@Component({<br>  selector: &#39;app-root&#39;,<br>  standalone: true,<br>  imports: [FormsModule],<br>  template: &#39;&lt;h1&gt;{{ result() }}&lt;/h1&gt;&#39;,<br>})<br>export class AppComponent implements OnInit {<br>  result = signal(0);<br>  ngOnInit(): void {<br>    if (window.Worker) {<br>      const worker = new Worker(new URL(&#39;./app.worker&#39;, import.meta.url));<br>      worker.onmessage = ({ data }) =&gt; {<br>        this.result.set(data);<br>      };<br>      worker.postMessage(10);<br>    }<br>  }<br>}</pre><p>Agora é só executar:</p><pre>npm start</pre><p>E esse é nosso resultado:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/470/1*Q4M74xyhd-uIYUDShmrdhg.png" /><figcaption>Resultado web worker com Angular</figcaption></figure><h3>Conclusão</h3><p>E é isso meu querido(a), as possiblidades com Web Workers são muitas no que diz respeito principalmente a performance das suas aplicações web, você não tem desculpa para poluir a thread principal do JavaScript com processamento pesado agora que tem essa ferramenta, aliás, isso não era recomendação nem quando os Web Workers ainda não existiam!</p><p>Claro que, seria ótimo poder processar o máximo que pudermos nos web workers, mas como nem tudo são flores, você pode se deparar com algumas limitações, por isso sugiro que leia mais sobre essa API para entender onde ela se encaixa na sua aplicação da melhor forma.</p><p>Muito obrigado por ter chegado até aqui, já que está aqui, apoie meu trabalho clicando no botãozinho de palmas ali em baixo, quem sabe não chega esse post não chegue para alguém que precisa entender sobre workers.</p><p>Me me siga aqui na plataforma para ficar por dentro dos próximos posts, bem como no Linkedin, produzo conteúdo por lá também</p><p>Te vejo numa próxima, dev! 😉</p><p><a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers">https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=080c266bbf04" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Angular: um olhar profundo sobre conversão de observables em signals]]></title>
            <link>https://danlima-dev.medium.com/angular-um-olhar-profundo-sobre-transforma%C3%A7%C3%A3o-de-observables-em-signals-25567ca9d938?source=rss-c263b528da4e------2</link>
            <guid isPermaLink="false">https://medium.com/p/25567ca9d938</guid>
            <category><![CDATA[angular-signals]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[front-end-development]]></category>
            <category><![CDATA[angular]]></category>
            <dc:creator><![CDATA[Danilo Lima]]></dc:creator>
            <pubDate>Fri, 26 Jan 2024 21:24:46 GMT</pubDate>
            <atom:updated>2024-01-27T01:47:03.922Z</atom:updated>
            <content:encoded><![CDATA[<p>Fala Dev, blz?</p><p>Desde a versão 15, o Angular tem passado por modernizações notáveis, incluindo as Standalone APIs e aprimoramentos no Server-Side Rendering (SSR), agora integrado ao framework. Também foram introduzidos o novo Control Flow e os Signals, visando melhorar a performance e a experiência de desenvolvimento. O Angular está implementando gradualmente os Signals, permitindo aos desenvolvedores alternar entre Observables e Signals. Um destaque é a introdução da função toSignal, facilitando essa transição e marcando um avanço significativo para uma programação mais eficaz e intuitiva.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*K_XEJrON8r3iMY45s8D1CA.png" /></figure><h3>O que são Signals?</h3><p>De forma resumida, eles se referem a um tipo de valor que pode ser monitorado por outras partes do sistema. Quando o estado ou valor do signal muda, ele automaticamente envia uma notificação para todos os inscritos.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/725/0*zXE39wsgMDmms-yk.png" /><figcaption>al<a href="https://www.angulararchitects.io/wp-content/uploads/2023/03/signals.png">https://www.angulararchitects.io/wp-content/uploads/2023/03/signals.png</a></figcaption></figure><p>Nesse sistema um Consumer pode ser qualquer parte do nosso código, como uma função, um método ou um objeto… talvez isso te lembra alguma coisa.</p><p>A palavra que talvez deve estar procurando se chama “reatividade”, que é um dos conceitos fundamentais que está presente em aplicações SPA, que define como a interface apresentada ao usuário responde a mudança de estado a partir de interações do usuário.</p><p>Os signals possui essa característica reativa, e é especialmente útil nesses tipos de aplicações que exigem uma experiência de usuário fluida e dinâmica capaz de reagir automaticamente as mudanças de estado dentro do sistema.</p><h3>Signals no contexto do Angular</h3><p>O SolidJS, uma biblioteca declarativa de construção de interfaces, foi uma das ferramentas que popularizaram os Signals nos últimos anos, mas esse não é um conceito novo. Desde então alguns frameworks e bibliotecas começaram a oferecer suporte a essa solução e dentre eles está o Angular.</p><p>Por isso, desde a versão 16 eles foram introduzidos no Angular através do Angular Signals, pensando principalmente no mecanismo de detecção de mudanças do framework, atualmente o Angular faz uso do ZoneJS, que monitora a execução de eventos no navegador e notifica o Angular de possíveis mudanças na sua árvore de componentes, disparando seu algorítmo de detecção de mudanças que é responsável por atualizar a interface para refletir o estado atual da aplicação.</p><p>No entanto, com o ZoneJS, o Angular não é capaz de saber onde exatamente aconteceu a mudança, somente que aconteceu alguma mudança e, assim, o algoritmo percorre toda a árvore de componentes desde o root, mesmo que apenas um componente tenha que ser re-renderizado.</p><p>Com a introdução dos signals no Angular isso tende a mudar, e a detecção de mudança e atualização da iterface tende a ser feita de maneira mais granular trazendo ganhos de performance realmente significativos.</p><h3>toSignal: Interoperabilidade com Observables</h3><p>Um dos esforços realizados pelo time do Angular e pela comunidade já nos permite, no momento que escrevo esse post, ter uma integração suave entre os Signals e os Observables, este último que é largamente utilizado no framework através da biblioteca RxJS, assim evita que tenhamos que reescrever todo nosso código, que normalmente usa Observables, para desfrutar do potencial que os Signals pode oferecer, com toSignal podemos facilmente transformar um Observable em um Signal.</p><p>Vamos dar uma olhada em um exemplo de código:</p><pre>import { AsyncPipe } from &#39;@angular/common&#39;;<br>import { Component } from &#39;@angular/core&#39;;<br>import { of } from &#39;rxjs&#39;;<br><br>@Component({<br>  selector: &#39;app-root&#39;,<br>  standalone: true,<br>  imports: [AsyncPipe],<br>  template: `<br>   &lt;div&gt;{{$message | async}}&lt;/div&gt;<br>  `,<br>  styleUrls: [&#39;./app.component.scss&#39;]<br>})<br>export class AppComponent  {<br><br>  $message = this.getMessage();<br><br>  getMessage(){<br>    return of(&quot;Hello World&quot;)<br>  }<br>}</pre><p>Aqui temos um componente bem simples e tradicional no Angular que exibe a mensagem “Hello World” no template usando o async pipe para automaticamente se inscrever e desinscrever do observable atribuido a <strong>$message, </strong>mas como que fazemos para utilizar signals sem ter que reescrever todo o nosso código atual?</p><p>Usamos a função toSignal para isso, veja o exemplo:</p><pre>import { AsyncPipe } from &#39;@angular/common&#39;;<br>import { Component } from &#39;@angular/core&#39;;<br>import { toSignal } from &#39;@angular/core/rxjs-interop&#39;<br>import { of } from &#39;rxjs&#39;;<br><br>@Component({<br>  selector: &#39;app-root&#39;,<br>  standalone: true,<br>  template: `<br>    &lt;div&gt;{{messageSignal()}}&lt;/div&gt;<br>  `,<br>  styleUrls: [&#39;./app.component.scss&#39;]<br>})<br>export class AppComponent {<br><br>  $message = this.getMessage();<br><br>  messageSignal = toSignal(this.$message)<br><br>  getMessage() {<br>    return of(&quot;Hello World&quot;)<br>  }<br>}</pre><p>O resultado na interface é exatamente o mesmo! O que muda é a forma que referenciar o messageSignal no template, com parenteses, semelhante a chamada de um função e, além disso, não precisamos mais do async pipe.</p><p>Legal né?! Vamos olhar como isso funciona por debaixo dos panos.</p><h3>toSignal por debaixo dos panos</h3><p>A conversão de Observables para Signals parece mágica, porém vamos da ruma olhada como isso funciona por debaixo dos panos e entender como esse processo é feito analisando o código fonte do Angular:</p><p>A função toSignal espera receber dois parâmetros, o primeiro seria o Observable que queremos converter num signal e o segundo é um parâmetro opcional que espera um objeto contendo algumas propriedades, como <strong>initialValue</strong> por exemplo.</p><p>Em seguida é definido algumas variáveis, como:</p><ul><li><strong>requiresCleanup</strong>: que espera um boleano que indica se a inscrição deve ser automaticamente finalizada (via DestroyRef) quando o contexto de criação de toObservable for destruído.</li><li>A próxima linha é executada caso requiresCleanup for true e o injector não tiver sido definido, isso para garantir que o código atual tenha acesso a um injetor, que é necessário para resolver dependências.</li><li>E a <strong>cleanupRef</strong> é atribuido uma referência a <strong>DestroyRef</strong></li></ul><pre>export function toSignal&lt;T, U = undefined&gt;(<br>  source: Observable&lt;T&gt;|Subscribable&lt;T&gt;,<br>  options?: ToSignalOptions&amp;{initialValue?: U}): Signal&lt;T|U&gt; {<br>  ngDevMode &amp;&amp;<br>      assertNotInReactiveContext(<br>          toSignal,<br>          &#39;Invoking `toSignal` causes new subscriptions every time. &#39; +<br>              &#39;Consider moving `toSignal` outside of the reactive context and read the signal value where needed.&#39;);<br><br>  const requiresCleanup = !options?.manualCleanup;<br>  requiresCleanup &amp;&amp; !options?.injector &amp;&amp; assertInInjectionContext(toSignal);<br>  const cleanupRef =<br>      requiresCleanup ? options?.injector?.get(DestroyRef) ?? ionject(DestroyRef) : null;</pre><p>Seguindo a nossa exploração:</p><ul><li>Define o estado do signal como sendo do tipo NoValue caso seja necessário que o observable emita de sincronamente quando <strong>toSignal</strong> se inscreve, garantindo que o observable produza um valor imediatamente após a inscrição.</li><li>Caso requireSync for falso o bloco <strong>else</strong> é executado e um signal recebendo um objeto como parâmetro, com um tipo um valor, é atribuído a variavel state.</li></ul><pre>  let state: WritableSignal&lt;State&lt;T|U&gt;&gt;;<br>  if (options?.requireSync) {<br>   state = signal({kind: StateKind.NoValue});<br>  } else {<br>   state = signal&lt;State&lt;T|U&gt;&gt;({kind: StateKind.Value, value: options?.initialValue as U});<br>  }</pre><p>Continuando…</p><ul><li>Aqui é feita uma inscrição no observable que foi passado para ser transformado num signal chamando o método subscribe e passando um objeto com três propriedades:</li><li><strong>next</strong>: contendo a função que irá atualizar o valor do signal, anteriormente armazenado na variável <strong>state,</strong> sempre que for emitido um novo valor para o observable original (source).</li><li><strong>error</strong>: contendo a função que será executada caso seja emitido um erro no observable original, aqui será tomado dois caminhos: se options.rejectErros for verdadeiro, então o erro será enviado de volta para o RxJS, que será tratada como exceções não capturadas, as famosas uncaught exceptions. Caso options.rejectErros for falso o erro não será enviado de volta para o RxJS mas será atribuído ao signal armazenado em <strong>state</strong></li></ul><pre>const sub = source.subscribe({<br>  next: value =&gt; state.set({kind: StateKind.Value, value}),<br>  error: error =&gt; {<br>    if (options?.rejectErrors) {<br>     throw error;<br>    }<br>    state.set({kind: StateKind.Error, error});<br>  },<br>});</pre><p>Quase acabando…</p><ul><li>Aqui, caso ngDevMode seja verdadeiro, ou seja, a aplicação estiver executando em ambiente de desenvolvimento, e se foi definido que o observable original deverá emitir assim que toSignal se inscrever mas o tipo do signal for <strong>NoValue </strong>então dispara um erro em tempo de execução</li><li>Depois disso, cancela inscrição quando o contexto atual for destruído, caso solicitado.</li></ul><pre>  if (ngDevMode &amp;&amp; options?.requireSync &amp;&amp; state().kind === StateKind.NoValue) {<br>    throw new ɵRuntimeError(<br>        ɵRuntimeErrorCode.REQUIRE_SYNC_WITHOUT_SYNC_EMIT,<br>        &#39;`toSignal()` called with `requireSync` but `Observable` did not emit synchronously.&#39;);<br>  }<br><br>  cleanupRef?.onDestroy(sub.unsubscribe.bind(sub));</pre><p>Em fim chegamos…</p><ul><li>Em resumo, o signal retornado é um ‘computed signal’ que mapeia estados para valores ou erros. Ele atualiza sempre que o ‘state’, que armazena o signal, é alterado — isto é, a cada novo valor emitido pelo observable original.</li><li>E detalhe, o último caso do switch em geral não executa pois o caso onde o tipo <strong>NoValue </strong>já foi tratado anteriormente.</li></ul><pre> return computed(() =&gt; {<br>    const current = state();<br>    switch (current.kind) {<br>      case StateKind.Value:<br>        return current.value;<br>      case StateKind.Error:<br>        throw current.error;<br>      case StateKind.NoValue:<br>       throw new ɵRuntimeError(<br>            ɵRuntimeErrorCode.REQUIRE_SYNC_WITHOUT_SYNC_EMIT,<br>            &#39;`toSignal()` called with `requireSync` but `Observable` did not emit synchronously.&#39;);<br>    }<br>  });</pre><h3>Conclusão</h3><p>Bem, o Angular vem evoluindo com a implementação dos Signals e a função que abordamos é fundamental no contexto de interoperabilidade entre Observables e Signals, claro que para essa interoperabilidade ser completa precisamos fazer o caminho contrário, ou seja, convertendo um Signal em um Observable, isso também é possível, porém esse será um papo para outro post.</p><p>É importante ressaltar que a função abordada nesse artigo ainda está em developer preview, porém logo deve ser lançada oficialmente.</p><p>Finalizando, se você chegou até aqui você é um guerreiro, então muito obrigado, espero que esse post possa te ajudar a entender com profundidade como foi implementado essa funcionalidade no Angular.</p><p>Me siga nas outras redes para ficar por dentro de mais conteúdo, até a próxima!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=25567ca9d938" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>