<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Erick Takeshi</title>
    <description>The latest articles on Forem by Erick Takeshi (@erick_tmr).</description>
    <link>https://forem.com/erick_tmr</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F192204%2F489641c5-4c4d-438c-b47b-85366aa53bce.jpg</url>
      <title>Forem: Erick Takeshi</title>
      <link>https://forem.com/erick_tmr</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/erick_tmr"/>
    <language>en</language>
    <item>
      <title>Ruby, Rails, Multi-threading e Puma: Como tudo isso se conecta?</title>
      <dc:creator>Erick Takeshi</dc:creator>
      <pubDate>Wed, 12 Feb 2025 00:44:52 +0000</pubDate>
      <link>https://forem.com/erick_tmr/ruby-rails-multi-threading-e-puma-como-tudo-isso-se-conecta-3kc7</link>
      <guid>https://forem.com/erick_tmr/ruby-rails-multi-threading-e-puma-como-tudo-isso-se-conecta-3kc7</guid>
      <description>&lt;p&gt;No desenvolvimento de aplicações Rails modernas, entender o funcionamento interno do Ruby, threads e servidores web tornou-se cada vez mais crucial. Seja você está construindo uma API de alto desempenho, processando jobs em background, ou otimizando a performance de uma aplicação existente, o conhecimento profundo desses conceitos pode fazer a diferença entre uma aplicação que escala bem e uma que falha sob carga.&lt;/p&gt;

&lt;p&gt;Nos meus últimos 9 anos de carreira, raramente precisei mergulhar tão fundo nesses conceitos. No entanto, em meu trabalho atual, desenvolvemos soluções que demandam um entendimento mais profundo de como o Rails e o Puma funcionam. Este artigo é um compilado desses aprendizados, visando ajudar outros desenvolvedores que precisam otimizar suas aplicações Rails para melhor concorrência e performance.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;TL;DR: Este artigo explora como o Ruby gerencia threads, como garantir thread safety em suas aplicações, e como configurar o Puma para máxima performance em diferentes cenários.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; O foco do post é sobre o interpretador MRI do Ruby (CRuby), que é o mais comumente utilizado em ambientes de produção.&lt;/p&gt;

&lt;h2&gt;
  
  
  Como o Ruby trabalha com threads?
&lt;/h2&gt;

&lt;p&gt;Meu objetivo aqui não é ensinar a sintaxe para criar threads, sincroniza-las ou utilizar mutexes. Em vez disso, quero fornecer uma visão geral sobre como as threads funcionam no Ruby e como tirar o maior proveito delas.&lt;/p&gt;

&lt;p&gt;Para entender threads no Ruby, o primeiro conceito essencial é o &lt;strong&gt;GVL&lt;/strong&gt; (Global VM Lock) ou &lt;strong&gt;GIL&lt;/strong&gt; (Global Interpreter Lock).&lt;/p&gt;

&lt;h3&gt;
  
  
  Global Vm Lock ou Global Interpreter Lock
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌────────────────────────────────────┐
│            Processo Ruby           │
│                                    │
│    ┌──────────┐     ┌──────────┐   │
│    │ Thread 1 │     │ Thread 2 │   │
│    └──────────┘     └──────────┘   │
│           │             │          │
│           └─────┬───────┘          │
│                 ▼                  │
│         ┌──────────────┐           │
│         │     GVL      │           │
│         └──────────────┘           │
│                 │                  │
│         ┌──────────────┐           │
│         │ Interpretador│           │
│         └──────────────┘           │
└────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O GVL é um mecanismo presente nas versões MRI do Ruby que impede que duas threads rodem &lt;em&gt;código Ruby&lt;/em&gt; ao mesmo tempo. Isso significa que não há paralelismo real quando se trata de execução de código Ruby puro. No entanto, o processo Ruby pode executar qualquer outra operação que não envolva código Ruby em paralelo, como operações de I/O.&lt;/p&gt;

&lt;h3&gt;
  
  
  Qual a vantagem de se utilizar threads?
&lt;/h3&gt;

&lt;p&gt;Como descrito no parágrafo anterior, é possível executar I/O (leituras do disco, requests HTTP, acesso ao banco de dados) em paralelo a código Ruby, então, quando dividimos o programa em várias threads, enquanto uma thread espera o resultado de alguma chamada I/O, outra thread pode ir executando código em paralelo, oferecendo assim a possibilidade de tirarmos maior vantagem de processadores multi-cores.&lt;/p&gt;

&lt;p&gt;Quando analisamos a distribuição de recursos em uma aplicação web (Olá Rails), podemos perceber que a maior parte do processamento é gasto com operações I/O, principalmente escrever / ler algo de algum banco de dados, então faz sentido que sua aplicação esteja preparada pra trabalhar em um contexto multi-threaded, e por sorte, neste contexto, o GIL não será um problema e aplicações Ruby podem se tornar muito eficientes!&lt;/p&gt;

&lt;h2&gt;
  
  
  Thread safety
&lt;/h2&gt;

&lt;p&gt;A origem de todo mal quando se trata de ambientes multi-threaded é o &lt;strong&gt;estado compartilhado&lt;/strong&gt;, ou seja, quando diferentes threads acessam a mesma região de memória simultaneamente. No contexto do Ruby e do sistema operacional, as threads compartilham o mesmo espaço de memória, o que significa que variáveis podem ser acessadas simultaneamente, levando a &lt;strong&gt;comportamentos imprevisíveis e difíceis de depurar&lt;/strong&gt;—o que chamamos de problemas de concorrência.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ilustrando problemas de concorrência
&lt;/h3&gt;

&lt;p&gt;Embora problemas de concorrência sejam amplamente conhecidos e discutidos, muitas vezes são difíceis de visualizar sem um exemplo concreto.&lt;br&gt;
&lt;strong&gt;PROBLEMAS DE CONCORRÊNCIA SÃO INERENTEMENTE DIFÍCEIS!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Imagine que temos a seguinte classe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;S3Uploader&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="vi"&gt;@files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;
        &lt;span class="n"&gt;threads&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

        &lt;span class="vi"&gt;@files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
            &lt;span class="n"&gt;threads&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;send_to_s3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;upload_results&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;

        &lt;span class="n"&gt;threads&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:join&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;upload_results&lt;/span&gt;
        &lt;span class="vi"&gt;@results&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="no"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_to_s3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# IO&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A lógica parece correta à primeira vista:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Criamos uma thread para cada operação de I/O, aproveitando melhor o tempo de CPU.&lt;/li&gt;
&lt;li&gt;Os resultados do upload são armazenados na fila &lt;code&gt;@results&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Porém, ao executar o código, percebemos que &lt;strong&gt;ele não é determinístico!&lt;/strong&gt; Ou seja, rodando o mesmo código várias vezes, obtemos resultados diferentes. Em um cenário com dois arquivos, às vezes &lt;code&gt;@results&lt;/code&gt; contém dois elementos (como esperado), mas em outras execuções contém apenas um.&lt;/p&gt;

&lt;p&gt;Pior: esse único resultado pode pertencer a qualquer um dos arquivos!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;E de onde vem esse problema?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;O problema surge por conta do operador &lt;code&gt;||=&lt;/code&gt; , que em ruby é um &lt;em&gt;check and assign,&lt;/em&gt; mas pra entendermos de fato precisamos nos aprofundar aqui.&lt;/p&gt;

&lt;p&gt;Estamos acostumados a olhar pra execução de código de maneira procedural, ou seja, cada linha é executada de cima pra baixo, uma por vez, então temos uma certa linearidade na execução do programa. O problema surge quando criamos e utilizamos outras threads, nesse caso, múltiplas linhas do nosso programa podem ser executadas ao mesmo tempo (ou não) e em ordens totalmente aleatórias!&lt;br&gt;
Vamos a um exemplo prático de execução deste programa:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;foi chamado o método &lt;code&gt;call&lt;/code&gt; da classe&lt;/li&gt;
&lt;li&gt;é criado e inicializado o array vazio &lt;code&gt;threads&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Iteração sobre a variável de instancia &lt;code&gt;@files&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;em cada iteração é criada uma thread nova para cada file, dando um append no array &lt;code&gt;threads&lt;/code&gt;

&lt;ol&gt;
&lt;li&gt;thread faz o upload para o S3 (chamada http, I/O)&lt;/li&gt;
&lt;li&gt;da um append na queue &lt;code&gt;@results&lt;/code&gt; com o retorno da chamada http&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;thread principal aguarda a finalização das outras threads instanciadas&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Num contexto multi-threaded, os passos 4a e 4b podem ser executados tanto em paralelo (ao mesmo tempo) quanto em ordens totalmente aleatórias, isso é um comportamento inerente de como o sistema operacional distribui os recursos computacionais da sua máquina, caso queira saber mais sobre o assunto procure sobre &lt;a href="https://developer.ibm.com/tutorials/l-completely-fair-scheduler/" rel="noopener noreferrer"&gt;task / thread scheduler&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Como não temos controle sobre essa distribuição, diversos cenários podem ser problemáticos nesse caso, gerando as famosas &lt;em&gt;race conditions&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Execução passo a passo de uma race condition&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Vamos a um exemplo peculiar onde uma race condition acontece e faz com que o valor da variável &lt;code&gt;@results&lt;/code&gt; seja computada de maneira errada (OBS: lembrando que o valor esperado seria o &lt;code&gt;@results&lt;/code&gt; ter 2 valores de respostas das chamadas ao S3):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;thread A é spawnada e começa a chamada http (I/O)&lt;/li&gt;
&lt;li&gt;GIL é liberado&lt;/li&gt;
&lt;li&gt;thread B é spawnada e começa a chamada http (I/O)&lt;/li&gt;
&lt;li&gt;thread B finaliza a chamada http e faz a verificação da variável &lt;code&gt;@results&lt;/code&gt; (ao chamar o método &lt;code&gt;upload_results&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;thread B enxergar a variável como &lt;code&gt;nil&lt;/code&gt; e instancia uma nova queue&lt;/li&gt;
&lt;li&gt;thread B para de executar por conta do thread scheduler&lt;/li&gt;
&lt;li&gt;thread A volta a executar&lt;/li&gt;
&lt;li&gt;thread A completa o request&lt;/li&gt;
&lt;li&gt;thread A enxergar a variável como &lt;code&gt;nil&lt;/code&gt; e instancia uma nova queue (por conta do operador &lt;code&gt;||=&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;thread A salva o resultado do request na variável &lt;code&gt;@results&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;thread B volta a executar e salva na variável results &lt;strong&gt;uma nova instância de Queue&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;thread B salva o resultado do request na variável &lt;code&gt;@results&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Percebem o problema que surgiu no passo 11? A thread B, voltou a sua execução em um momento totalmente inoportuno, após fazer a checagem e antes de ter sido feito o assignment, dando chance para que a thread A fizesse o assignment novamente!&lt;/p&gt;

&lt;p&gt;Todo o problema surge por conta do operador &lt;code&gt;||=&lt;/code&gt; , ele não é um operador &lt;strong&gt;atômico&lt;/strong&gt;, na prática significa que ele não faz sua tarefa em uma única operação, no caso específico deste operador, é como se ele tivesse a seguinte implementação*&lt;em&gt;:&lt;/em&gt;*&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;|&lt;/span&gt;&lt;span class="o"&gt;|=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;variable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;variable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nil?&lt;/span&gt;
        &lt;span class="n"&gt;variable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="n"&gt;variable&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Percebam que existem duas etapas nessa operação:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;checar se a variável está &lt;code&gt;nil&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;fazer o assignment ou devolver o valor&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No contexto de &lt;strong&gt;múltiplas threads&lt;/strong&gt;, essas etapas podem ser interrompidas no meio, resultando em um comportamento imprevisível, portante é recomendado evitá-lo num contexto multi-threaded.&lt;/p&gt;

&lt;h3&gt;
  
  
  Como garantir que meu código é thread safe?
&lt;/h3&gt;

&lt;p&gt;Diante do problema apresentado, como garantir que nosso código não sofra com &lt;em&gt;race conditions&lt;/em&gt; ou inconsistências? Basicamente, temos duas opções:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Evitar estado compartilhado&lt;/strong&gt;: Essa é a abordagem mais simples, pois elimina completamente os riscos de concorrência. No entanto, em aplicações reais, é praticamente impossível evitar estado compartilhado em 100% dos casos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Controlar o acesso ao estado compartilhado&lt;/strong&gt;: Aqui entra o conceito de &lt;em&gt;thread safety&lt;/em&gt;, que consiste em permitir acesso concorrente ao estado da aplicação sem comprometer sua integridade.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A opção 1 é a mais simples, porém, qualquer aplicação real que faça algo útil, vai depender de estado, em 99,9% dos casos. A opção 2 é onde o thread safety entra.&lt;/p&gt;

&lt;p&gt;Thread safety é a garantia de que um código pode ser executado por múltiplas threads simultaneamente sem corromper dados, introduzir comportamentos imprevisíveis ou gerar condições de corrida. Para isso, existem diversas técnicas, como &lt;strong&gt;locks, mutexes, semáforos, filas concorrentes&lt;/strong&gt; e outras estruturas de sincronização.&lt;/p&gt;

&lt;p&gt;Este artigo não pretende cobrir todas essas técnicas, mas aqui estão algumas diretrizes gerais:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Verifique se as bibliotecas que você usa são thread-safe&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;no caso de gems, provavelmente no README vai ter algum lugar alegando que a gem é thread safe, caso contrário, busque nas issues do repositório do projeto&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Use estruturas de dados thread-safe&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;existe uma gem chamada &lt;a href="https://github.com/ruby-concurrency/concurrent-ruby" rel="noopener noreferrer"&gt;concurrent ruby&lt;/a&gt; (que por sinal já vem como dependência default do Rails), ela implementa diversos padrões e estruturas de dados thread safe, é sempre o primeiro lugar que eu olho quando preciso de algo, 99% dos casos já tem algo pronto ali que possa ser utilizado&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Evite estado mutável e global sempre que possível&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;evite, quando possível, estado mutável e global, estou falando aqui de constantes, métodos e variáveis de classe, variáveis globais e a AST. O lugar de estado global compartilhado, normalmente, é algum banco de dados, como Postgres, Redis, etc.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Cuidado com metaprogramação e multi-threading&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;A AST é um caso excepcional, mas como Ruby é uma linguagem extremamente dinâmica (cheia de meta programming), imagine o risco de diferentes threads rodando com versões diferentes do código ao mesmo tempo! Se precisar usar metaprogramação, entenda os impactos na concorrência.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Rails, Puma e Multi-threading
&lt;/h2&gt;

&lt;p&gt;Aqui é a parte que vamos conectar a teoria das seções anteriores com a prática.&lt;/p&gt;

&lt;p&gt;O Puma é o servidor default do Rails (faz um tempinho já). O Puma é um servidor multi-threaded. Existe no próprio repositório do Puma &lt;a href="https://github.com/puma/puma/blob/master/docs/architecture.md" rel="noopener noreferrer"&gt;um arquivo&lt;/a&gt; detalhando sua arquitetura e seu funcionamento, vale muito a leitura para qualquer dev Rails.&lt;/p&gt;

&lt;p&gt;O Puma possui basicamente dois modos de trabalho, o &lt;em&gt;cluster&lt;/em&gt; e o &lt;em&gt;single&lt;/em&gt;. Em termos de multi-threading não faz muita diferença, pois em ambos os modos o Puma irá trabalhar com threads, porém no cluster mode, ele irá fazer um fork e criar outros processos, que servirão requests em paralelo também.&lt;/p&gt;

&lt;h3&gt;
  
  
  Como o Puma gerencia requests?
&lt;/h3&gt;

&lt;p&gt;Agora chegamos na parte principal, como o Puma trabalha com threads.&lt;/p&gt;

&lt;p&gt;De forma simplificada:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Existe um &lt;strong&gt;thread pool&lt;/strong&gt; (conjunto de threads previamente alocadas).&lt;/li&gt;
&lt;li&gt;Cada request entra em uma fila (&lt;code&gt;todo set&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;À medida que threads ficam disponíveis, elas processam os requests na ordem de chegada.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;O mais importante aqui é entender que &lt;strong&gt;cada request roda em uma thread separada&lt;/strong&gt;. Isso significa que &lt;strong&gt;enquanto um request está bloqueado esperando uma operação de I/O (como uma consulta no banco), outras threads continuam processando outros requests&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  O que isso significa para o Rails?
&lt;/h3&gt;

&lt;p&gt;O &lt;strong&gt;Rails foi projetado para ser thread-safe&lt;/strong&gt;. Todo o código dos controllers é instanciado e escopado por request, o que garante que &lt;strong&gt;o próprio Rails não introduz condições de corrida&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;No entanto, essa segurança pode ser comprometida quando adicionamos nossas próprias abstrações dentro do controller. Para evitar problemas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sempre prefira &lt;strong&gt;instanciar objetos dentro do escopo do request&lt;/strong&gt;, em vez de depender de métodos de classe ou variáveis globais.&lt;/li&gt;
&lt;li&gt;Se precisar compartilhar estado entre requests, &lt;strong&gt;use um armazenamento externo&lt;/strong&gt; como Redis ou um banco de dados relacional.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Se fugir dessas práticas for necessário, &lt;strong&gt;tenha certeza de que está garantindo sincronização adequada para evitar concorrência insegura&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  O poder da concorrência e paralelismo
&lt;/h2&gt;

&lt;p&gt;Agora que entendemos como o Ruby, Rails e o Puma funcionam, podemos enxergar melhor os benefícios do modelo concorrente.&lt;/p&gt;

&lt;p&gt;Na maioria das aplicações web, &lt;strong&gt;o maior gargalo é I/O&lt;/strong&gt;—seja acessando um banco de dados, chamando APIs externas ou lendo arquivos. Como o Puma é &lt;strong&gt;multi-threaded&lt;/strong&gt;, ele permite que outras threads processem novos requests enquanto uma thread aguarda uma operação de I/O ser concluída.&lt;/p&gt;

&lt;p&gt;Esse modelo é altamente eficiente, pois &lt;strong&gt;maximiza a utilização dos recursos computacionais&lt;/strong&gt;, evitando que a aplicação fique ociosa.&lt;/p&gt;

&lt;h3&gt;
  
  
  O ponto de virada: otimizando o número de threads
&lt;/h3&gt;

&lt;p&gt;Nem tudo são flores. Aumentar indiscriminadamente o número de threads não melhora indefinidamente o desempenho.&lt;/p&gt;

&lt;p&gt;Cada aplicação tem um &lt;strong&gt;ponto ótimo&lt;/strong&gt; de configuração, e aumentar demais o número de threads pode, na verdade, &lt;strong&gt;reduzir o throughput&lt;/strong&gt; e &lt;strong&gt;aumentar a latência&lt;/strong&gt;. Isso acontece porque:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mais threads significa mais concorrência por CPU, memória e acesso a recursos.&lt;/li&gt;
&lt;li&gt;Em cenários extremos, pode ocorrer &lt;strong&gt;starvation&lt;/strong&gt;, onde algumas threads ficam bloqueadas por longos períodos esperando sua vez.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A configuração &lt;code&gt;RAILS_MAX_THREADS&lt;/code&gt; no Puma define quantas threads podem ser usadas por processo. Para encontrar o melhor valor:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Teste diferentes configurações&lt;/strong&gt; e use benchmarking para medir o impacto.&lt;/li&gt;
&lt;li&gt;Lembre-se: &lt;strong&gt;o número ótimo depende da carga da sua aplicação e dos recursos do servidor, então simule em ambiente mais próximo possível a produção.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Existem diversos artigos detalhando sobre &lt;a href="https://engineering.appfolio.com/appfolio-engineering/2017/3/22/rails-benchmarking-puma-and-multiprocess" rel="noopener noreferrer"&gt;benchmarking&lt;/a&gt; de diferentes combinações de números  de processos e threads no Puma, porém, lembre-se que isso é específico de cada aplicação, teste e descubra o número ótimo para o seu caso!&lt;/p&gt;

&lt;h2&gt;
  
  
  Concluindo
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;O &lt;strong&gt;Rails é thread-safe por padrão&lt;/strong&gt;, mas seu código precisa seguir boas práticas para manter essa segurança.&lt;/li&gt;
&lt;li&gt;O &lt;strong&gt;Puma permite concorrência eficiente&lt;/strong&gt;, maximizando o uso dos recursos disponíveis.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evitar estado compartilhado na memória da aplicação entre requests&lt;/strong&gt; elimina grande parte dos riscos de concorrência.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ajustar corretamente o número de threads&lt;/strong&gt; é essencial para garantir um bom desempenho sem sobrecarregar o servidor.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Entender esses conceitos não apenas evita bugs difíceis de depurar, mas também permite &lt;strong&gt;extrair o máximo de desempenho da aplicação&lt;/strong&gt;, garantindo escalabilidade e eficiência.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>performance</category>
      <category>programming</category>
      <category>linux</category>
    </item>
    <item>
      <title>Promoção no trabalho começa aqui: Como a dopamina pode impulsionar seu sucesso</title>
      <dc:creator>Erick Takeshi</dc:creator>
      <pubDate>Thu, 21 Nov 2024 21:22:30 +0000</pubDate>
      <link>https://forem.com/erick_tmr/promocao-no-trabalho-comeca-aqui-como-a-dopamina-pode-impulsionar-seu-sucesso-2fd</link>
      <guid>https://forem.com/erick_tmr/promocao-no-trabalho-comeca-aqui-como-a-dopamina-pode-impulsionar-seu-sucesso-2fd</guid>
      <description>&lt;p&gt;Imagine que seu cérebro é como um carro esportivo potente, mas que está rodando com combustível de baixa qualidade (aquela gasolina comum safada).&lt;/p&gt;

&lt;p&gt;Você tenta acelerar rumo aos seus objetivos — como estudar para aprender uma nova linguagem de programação ou se preparar para uma promoção ou uma transição para uma empresa dos sonhos —, mas o motor simplesmente não responde. &lt;/p&gt;

&lt;p&gt;Esse “combustível” é a dopamina, o neurotransmissor que impulsiona nossa motivação e desejo por recompensas. O problema é que, quando nossos receptores estão saturados pelas recompensas fáceis do dia a dia — como redes sociais, vídeos curtos ou horas intermináveis jogando —, nossa capacidade de buscar metas mais significativas acaba comprometida.&lt;/p&gt;

&lt;p&gt;Neste texto, quero te explicar como a dopamina funciona no cérebro e, mais importante, como você pode dar início ao processo de retomar o controle e alcançar seus objetivos. A ideia é transformar sua dopamina na gasolina podium que seu “motor” precisa para liberar todo o potencial e te levar mais longe.&lt;/p&gt;

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

&lt;p&gt;A dopamina é um neurotransmissor — ou seja, uma substância química responsável por transmitir sinais entre os neurônios no cérebro. Ela desempenha um papel crucial no sistema de recompensa, sendo liberada sempre que notamos que precisaremos realizar um esforço para garantir algo que o cérebro interpreta como positivo ou prazeroso. Em outras palavras, a dopamina é como o "empurrão" químico que nos faz agir em direção aos nossos objetivos, grandes ou pequenos. Sem ela, ficamos paralisados, incapazes de iniciar o esforço necessário.&lt;/p&gt;

&lt;p&gt;Um estudo realizado com macacos ajudou a revelar o papel fundamental da dopamina no esforço em busca de objetivos. Os macacos eram colocados em uma sala com três elementos: uma luz, um botão e uma mamadeira com suco. Eles foram ensinados que, ao verem a luz acender, precisariam apertar o botão 10 vezes para ter acesso ao suco.&lt;/p&gt;

&lt;p&gt;O que os pesquisadores descobriram foi fascinante: o pico de dopamina no cérebro do macaco ocorria não quando ele recebia o suco, mas sim quando a luz acendia. Ou seja, a dopamina era liberada no momento em que o macaco reconhecia o estímulo (a luz) que iniciava o esforço. Isso mostrou que a dopamina é essencial para nos mover em direção aos nossos objetivos. Ela não recompensa o fim do caminho, mas, sobretudo, nos dá energia para começar.&lt;/p&gt;

&lt;h2&gt;
  
  
  A importância de conhecermos nossas luzes, botões e sucos
&lt;/h2&gt;

&lt;p&gt;No estudo, o macaco só apertava o botão quando via a luz acender. Ele não apertava o botão de forma aleatória ou sem motivo. A luz, nesse contexto, era o estímulo que o motivava a agir.&lt;/p&gt;

&lt;p&gt;Agora pense na sua vida: qual é a sua “luz”? O que te motiva a iniciar o esforço necessário para alcançar seus objetivos? A motivação pode vir de diferentes fontes: um amigo que está estudando junto com você, um colega que conquistou algo que você também almeja, ou até mesmo a visualização clara dos benefícios que determinada meta pode trazer à sua vida. Identificar suas “luzes” é fundamental porque, apesar do que muitos dizem, não é só disciplina que nos faz avançar — a motivação desempenha um papel essencial.&lt;/p&gt;

&lt;p&gt;Depois de identificar suas luzes, é hora de conhecer o “botão”. Assim como o macaco sabia que apertar o botão 10 vezes garantiria o suco, você precisa entender claramente qual esforço é necessário para atingir seu objetivo. Esforço sem recompensa é frustrante, e a falta de resultados pode desmotivar, reduzindo ainda mais os níveis de dopamina.&lt;/p&gt;

&lt;p&gt;E, finalmente, precisamos entender nosso “suco”. Qual é o objetivo que você quer alcançar? Quais benefícios ele trará para sua vida? &lt;/p&gt;

&lt;p&gt;Ter clareza sobre o que você realmente deseja é crucial, mas aqui vai uma dica importante: alinhe suas expectativas com a realidade. Não idealize um “suco de várias frutas polivitamínico com multiminerais” se, no final, o suco é apenas de laranja. Conheça seus objetivos e saiba exatamente o que esperar deles, isso evita frustrações e ajuda você a manter o foco no que realmente importa.&lt;/p&gt;

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

&lt;p&gt;Entender como a dopamina funciona e sua relação com esforço, motivação e recompensa é o primeiro passo para assumir o controle do processo de alcançar seus objetivos. Assim como no experimento do macaco, é fundamental identificar suas luzes (os estímulos que te motivam), seus botões (os esforços necessários) e seus sucos (os objetivos que você quer alcançar).&lt;/p&gt;

&lt;p&gt;Lembre-se: a jornada é tão importante quanto o destino. Ao reequilibrar seus hábitos e aprender a usar a dopamina a seu favor, você estará transformando seu cérebro em um carro esportivo realmente preparado para alcançar qualquer objetivo — com gasolina &lt;em&gt;podium&lt;/em&gt; no tanque e o motor a todo vapor.&lt;/p&gt;

&lt;p&gt;Por fim, é importante destacar que as informações deste texto foram inspiradas nas aulas do &lt;a href="https://www.youtube.com/@CanaldoEslen" rel="noopener noreferrer"&gt;Eslen Delanogare&lt;/a&gt;, especialista em neurociência. Caso você queira se aprofundar no tema, recomendo fortemente acompanhar o conteúdo dele para explorar ainda mais esse universo fascinante.&lt;/p&gt;

&lt;p&gt;Então, qual é o seu suco?&lt;/p&gt;

</description>
      <category>career</category>
      <category>motivation</category>
    </item>
    <item>
      <title>Back of the envelope estimations, começando a entrevista de system design</title>
      <dc:creator>Erick Takeshi</dc:creator>
      <pubDate>Wed, 21 Aug 2024 21:35:34 +0000</pubDate>
      <link>https://forem.com/erick_tmr/back-of-the-envelope-estimations-comecando-a-entrevista-de-system-design-5cc0</link>
      <guid>https://forem.com/erick_tmr/back-of-the-envelope-estimations-comecando-a-entrevista-de-system-design-5cc0</guid>
      <description>&lt;p&gt;Uma etapa muito comum em processos seletivos para engenheiros de software, especialmente nessa era pós pandemia, é a entrevista de system design. Se você é um profissional de nível sênior ou superior, é quase certo que enfrentará um desafio desse tipo, e dada a sua importância, gostaria de compartilhar algumas dicas sobre um dos primeiros passos cruciais, para essas entrevistas: back of the envelope estimations!&lt;/p&gt;

&lt;p&gt;OBS: Caso não faça ideia do que seja system design, deixo aqui indicação de dois sites muito legais para estudar mais / entender sobre o assunto:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/donnemartin/system-design-primer" rel="noopener noreferrer"&gt;Repositório System Design Primer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bytebytego.com/" rel="noopener noreferrer"&gt;Livros / curso do ByteByteGo, do Alex Xu&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  O que é &lt;em&gt;back of the envelope estimations&lt;/em&gt;?
&lt;/h2&gt;

&lt;p&gt;Segundo Jeff Dean, Google Senior Fellow, em tradução livre: "back-of-the-envelope estimations são estimativas que você cria usando uma combinação de hipóteses suas e números de performance comuns para ter uma boa noção de quais designs atenderão aos seus requisitos”.&lt;/p&gt;

&lt;p&gt;Basicamente, antes de iniciarmos a construção / arquitetura do sistema, é ideal estimarmos alguns requisitos de capacidade e performance. Isso nos permite tomar decisões mais acertadas e adequadas para o cenário em questão. A essas estimativas damos o nome informal de "back of the envelope estimations".&lt;/p&gt;

&lt;h2&gt;
  
  
  Potência de 2
&lt;/h2&gt;

&lt;p&gt;No mundo da computação trabalhamos com bits, isso é o básico. Quando estamos estimando algo como armazenamento de dados, precisamos trabalhar com potência de 2, pois estamos lidando com sequências de bits (normalmente sequências de bytes, que são 8 bits). Ter uma tabela de potências de 2 na ponta da língua pode ajudar muito:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Potência&lt;/th&gt;
&lt;th&gt;Valor aproximado&lt;/th&gt;
&lt;th&gt;Nome completo&lt;/th&gt;
&lt;th&gt;Abreviação&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;1 mil&lt;/td&gt;
&lt;td&gt;1 Kilobyte&lt;/td&gt;
&lt;td&gt;1 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;1 milhão&lt;/td&gt;
&lt;td&gt;1 Megabyte&lt;/td&gt;
&lt;td&gt;1 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;1 bilhão&lt;/td&gt;
&lt;td&gt;1 Gigabyte&lt;/td&gt;
&lt;td&gt;1 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;1 trilhão&lt;/td&gt;
&lt;td&gt;1 Terabyte&lt;/td&gt;
&lt;td&gt;1 TB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;1 quadrilhão&lt;/td&gt;
&lt;td&gt;1 Petabyte&lt;/td&gt;
&lt;td&gt;1 PB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Além dessa tabela, é útil ter em mente os tamanhos dos tipos de dados elementares para estimar requisitos de armazenamento:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tipo de dado&lt;/th&gt;
&lt;th&gt;Tamanho (byte)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;int&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;float&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;boolean&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UTF-8 character&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UNIX timestamp&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Números de latência que todo programador deveria saber
&lt;/h2&gt;

&lt;p&gt;No mundo da computação, latência geralmente se refere ao tempo que uma determinada ação leva para ser executada, ou seja, num contexto de rede, latência pode se referir ao tempo que um pacote demora pra ir de um nó A até o nó B, no contexto de armazenamento, latência pode ser referir ao tempo que o disco demora para gravar uma informação X ou ler a mesma informação. No contexto de banco de dados, latência pode ser referir ao tempo que uma query demora pra ser executada e retornar seu resultado, e por ai vai.&lt;/p&gt;

&lt;p&gt;Dentro deste contexto de latência, o Dr. Jeff Dean lista alguns números importantes pra termos na cabeça:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Nome da operação&lt;/th&gt;
&lt;th&gt;Latência&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;L1 cache reference&lt;/td&gt;
&lt;td&gt;0.5 ns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Branch mispredict&lt;/td&gt;
&lt;td&gt;5 ns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L2 cache reference&lt;/td&gt;
&lt;td&gt;7 ns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mutex lock/unlock&lt;/td&gt;
&lt;td&gt;100 ns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Main memory reference&lt;/td&gt;
&lt;td&gt;100 ns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compress 1K bytes with Zippy&lt;/td&gt;
&lt;td&gt;10,000 ns = 10 µs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Send 2K bytes over 1 Gbps network&lt;/td&gt;
&lt;td&gt;20,000 ns = 20 µs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read 1 MB sequentially from memory&lt;/td&gt;
&lt;td&gt;250,000 ns = 250 µs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Round trip within the same datacenter&lt;/td&gt;
&lt;td&gt;500,000 ns = 500 µs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Disk seek&lt;/td&gt;
&lt;td&gt;10,000,000 ns = 10 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read 1 MB sequentially from the network&lt;/td&gt;
&lt;td&gt;10,000,000 ns = 10 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read 1 MB sequentially from disk&lt;/td&gt;
&lt;td&gt;30,000,000 ns = 30 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Send packet CA (California) -&amp;gt;Netherlands-&amp;gt;CA&lt;/td&gt;
&lt;td&gt;150,000,000 ns = 150 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Um primeiro disclaimer sobre esta tabela é que os números definitivamente estão obsoletos, uma vez que os computadores evoluem e se tornam mais rápidos e poderosos constantemente, porém, não temos nenhum ônus em utilizarmos esta tabela para back of the envelope estimations, pois estamos trabalhando com estimativas, bem rudimentares e aproximadas, que serão utilizadas em entrevistas de system design.&lt;/p&gt;

&lt;p&gt;A ideia é termos noção de ordem de grandeza e não de números exatos.&lt;/p&gt;

&lt;h3&gt;
  
  
  Interpretações dos números
&lt;/h3&gt;

&lt;p&gt;A partir desses dados, podemos tirar algumas conclusões valiosas para nossos designs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Há uma diferença de magnitude significativa entre diferentes operações (exemplo ler um dado da rede / disco vs ler um dado da memória)&lt;/li&gt;
&lt;li&gt;Datacenters costumam estar localizados muito longe um dos outros, tornando custoso o tráfego de dados entre eles.&lt;/li&gt;
&lt;li&gt;Utilizando um algoritmo simples de compressão conseguimos economizar muito em largura de banda no tráfego de rede&lt;/li&gt;
&lt;li&gt;Escritas são 40x mais lentas do que leituras&lt;/li&gt;
&lt;li&gt;Dados globais compartilhados são caros, isto é uma limitação fundamental de sistemas distribuídos, a contenção de locks em objetos que são muito escritos acaba matando a performance do sistema, uma vez que precisamos executar transações seriais (uma de cada vez)&lt;/li&gt;
&lt;li&gt;Desenhe sua arquitetura para escalar escritas, pois são as operações mais custosas&lt;/li&gt;
&lt;li&gt;Otimize seu sistema para minimizar locks de escrita, garantindo que dados possam ser escritos simultaneamente sem um lock&lt;/li&gt;
&lt;li&gt;Otimize seu sistema para paralelismo, ou seja, garanta que escritas possam ser executadas de maneira paralela sempre que possível&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Números de disponibilidade
&lt;/h2&gt;

&lt;p&gt;Disponibilidade é a medida que indica o tempo em que um sistema está online e responsivo dentro de um determinado período. Normalmente, disponibilidade é expressa em porcentagem, onde 100% significa que o sistema esteve disponível todo o tempo analisado. A maioria dos sistemas considera aceitável uma disponibilidade entre 99% e 100%.&lt;/p&gt;

&lt;p&gt;Um termo frequentemente usado no contexto de disponibilidade é SLA (Service Level Agreement), que é um valor acordado em contrato sobre a disponibilidade de um serviço. SLAs são frequentemente expressos em "números de 9", que indicam a quantidade de 9s após a vírgula (decimais).&lt;/p&gt;

&lt;p&gt;A tabela a seguir expressa os valores aproximados de SLA para diferentes períodos de tempo:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Disponibilidade %&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Downtime por dia&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Downtime por semana&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Downtime por mês&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Downtime por ano&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;99%&lt;/td&gt;
&lt;td&gt;14.40 minutos&lt;/td&gt;
&lt;td&gt;1.68 horas&lt;/td&gt;
&lt;td&gt;7.31 horas&lt;/td&gt;
&lt;td&gt;3.65 dias&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;99.99%&lt;/td&gt;
&lt;td&gt;8.64 segundos&lt;/td&gt;
&lt;td&gt;1.01 minutos&lt;/td&gt;
&lt;td&gt;4.38 minutos&lt;/td&gt;
&lt;td&gt;52.60 minutos&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;99.999%&lt;/td&gt;
&lt;td&gt;864.00 milissegundos&lt;/td&gt;
&lt;td&gt;6.05 segundos&lt;/td&gt;
&lt;td&gt;26.30 segundos&lt;/td&gt;
&lt;td&gt;5.26 minutos&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;99.9999%&lt;/td&gt;
&lt;td&gt;86.40 milissegundos&lt;/td&gt;
&lt;td&gt;604.80 milissegundos&lt;/td&gt;
&lt;td&gt;2.63 segundos&lt;/td&gt;
&lt;td&gt;31.56 segundos&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Exemplo prático de análise
&lt;/h2&gt;

&lt;p&gt;Imagine que você precisa gerar uma página de resultados de uma busca com 30 thumbnails (imagens), como seria a análise de performance deste requisito?&lt;/p&gt;

&lt;h3&gt;
  
  
  Proposta 1 - Design Serial
&lt;/h3&gt;

&lt;p&gt;Nesta proposta iremos ler as imagens do disco sequencialmente (uma após a outra).&lt;/p&gt;

&lt;p&gt;A conta de performance seria:&lt;/p&gt;

&lt;p&gt;30 X ( 10 ms / leitura de disco + ( 0,256 MB * 30 ms / MB ) ) = 530 ms&lt;/p&gt;

&lt;h4&gt;
  
  
  Explicando a conta:
&lt;/h4&gt;

&lt;p&gt;Como são 30 thumbnails, multiplicamos por 30 a quantidade de tempo de leitura de cada thumbnail.&lt;/p&gt;

&lt;p&gt;Cada thumbnail gasta o tempo da leitura em disco mais o tempo necessário para transferir esses dados do disco pra memória, ambos os valores estão nas tabelas acima, mas pra referência seriam 10 ms / leitura de disco e 30 ms / MB transferido.&lt;/p&gt;

&lt;p&gt;Além dos números da tabela, estimamos que cada thumbnail tenha 256 KB de tamanho (ou 0.256 MB).&lt;/p&gt;

&lt;p&gt;Resolvendo a equação, chegamos ao número de 530 ms como estimativa para gerar a página, levando em conta apenas a leitura dos thumbnails.&lt;/p&gt;

&lt;h3&gt;
  
  
  Proposta 2 - Design Paralelo
&lt;/h3&gt;

&lt;p&gt;Nesta proposta, as leituras em disco seriam feitas em paralelo, usando, por exemplo, diferentes threads para ler simultaneamente do disco.&lt;/p&gt;

&lt;p&gt;A conta ficaria:&lt;/p&gt;

&lt;p&gt;10 ms / leitura de disco + 0,256 MB * 30 ms / MB = 18 ms&lt;/p&gt;

&lt;h4&gt;
  
  
  Explicando a conta:
&lt;/h4&gt;

&lt;p&gt;Aqui, a conta é similar à do design sequencial, mas não multiplicamos o resultado por 30, já que as leituras são feitas em paralelo.&lt;/p&gt;

&lt;p&gt;Para tornar o cenário mais realista, podemos adicionar alguns milissegundos para sincronização de threads e variação nas leituras de disco, onde teríamos algo entre 30 e 60 ms, ainda assim muito mais rápido que o design serial, mostrando o poder do paralelismo!&lt;/p&gt;

&lt;h3&gt;
  
  
  Analisando as propostas
&lt;/h3&gt;

&lt;p&gt;Qual cenário é melhor?&lt;/p&gt;

&lt;p&gt;A resposta pra essa pergunta vai depender de cada caso e de seus requisitos, entretanto, tendo as estimativas de cada design, podemos compara-los de maneira mais assertiva e optar por aquele que entrega melhor performance e garante que os requisitos estejam sendo satisfeitos.&lt;/p&gt;

&lt;p&gt;O importante é que agora você tem uma base sólida para comparação, permitindo responder de forma mais precisa a perguntas que possam surgir durante uma reunião de whiteboarding ou uma entrevista de system design, usando números concretos para embasar suas decisões.&lt;/p&gt;

&lt;h2&gt;
  
  
  Concluindo
&lt;/h2&gt;

&lt;p&gt;Back-of-the-envelope estimations é uma técnica que ajuda a termos uma visão mais holística e baseada em números de diferentes designs e soluções, sendo uma ferramenta importante pra quando estamos tentando desenhar algum sistema.&lt;/p&gt;

&lt;p&gt;Tenha em mãos números básicos de performance e armazenamento de partes relevantes utilizadas em seus sistemas, saber de antemão estes números, antes das reuniões de whiteboarding / entrevistas de system design, ajuda a fazer estimativas mais precisas, uma vez que é impossível estimar corretamente sem uma base de informações.&lt;/p&gt;

&lt;p&gt;Além disso, monitorar e medir periodicamente seus sistemas garante que essas estimativas se tornem cada vez mais precisas, pois você estará utilizando dados reais e específicos dos componentes do seu sistema. Isso não só melhora a qualidade das estimativas, mas também fortalece a sua capacidade de tomar decisões informadas e eficazes.&lt;/p&gt;

</description>
      <category>interview</category>
      <category>systemdesign</category>
      <category>learning</category>
    </item>
    <item>
      <title>heap &amp; stack, Reduzindo o fardo do garbage collector em Go</title>
      <dc:creator>Erick Takeshi</dc:creator>
      <pubDate>Wed, 24 Jul 2024 20:47:26 +0000</pubDate>
      <link>https://forem.com/erick_tmr/heap-stack-reduzindo-o-fardo-do-garbage-collector-em-go-401h</link>
      <guid>https://forem.com/erick_tmr/heap-stack-reduzindo-o-fardo-do-garbage-collector-em-go-401h</guid>
      <description>&lt;p&gt;No nosso dia a dia de programação, raramente precisamos nos preocupar com a performance ou a alocação de memória, afinal de contas as ferramentas do Go como runtime, garbage collector, compilador, são bem eficientes em seu trabalho, nos blindando da carga cognitiva nestas áreas. Porém. nas vezes em que performance é uma preocupação e temos que descer um nível abaixo no código, saber como essas ferramentas funcionam por debaixo dos panos e saber como você pode otimizar seu código é essencial.&lt;/p&gt;

&lt;p&gt;No post de hoje quero discutir um pouco sobre alocação de memória e o garbage collector, além de deixar umas dicas de como diminuir o trabalho do garbage collector.&lt;/p&gt;

&lt;h2&gt;
  
  
  heap e stack
&lt;/h2&gt;

&lt;p&gt;Para as pessoas com um background mais de Ciência / Engenharia da Computação, esses termos devem despertar alguma memória (seja ela dolorosa ou não hehe) das aulas de sistemas operacionais, arquitetura de computadores, etc. Para nivelar o entendimento, vou introduzir brevemente alguns conceitos que julgo serem importantes para continuarmos com a discussão do garbage collector.&lt;/p&gt;

&lt;p&gt;heap e stack são duas áreas distintas de um memory layout de algum processo do sistema operacional, ou seja, dando um resumo bem por cima e bem impreciso (como diria os disclaimers do mestre Akita rsrs), são duas “áreas” diferentes da memória do seu computador, onde cada uma tem uma função específica e armazena um tipo diferente de dado.&lt;/p&gt;

&lt;p&gt;Assim como processos do SO possuem memory layout e áreas da memória reservada somente para eles, as threads desses processos também o tem, e, no caso do Go, como não lidamos diretamente com threads do SO, as goroutines também possuem seu próprio memory layout, com stack, heap, etc, gerenciadas pelo próprio runtime do Go (essa é uma grande vantagem das goroutines quando comparadas com threads comuns do SO, mas isso é tema para outro post).&lt;/p&gt;

&lt;h3&gt;
  
  
  Como funcionam a stack e a heap?
&lt;/h3&gt;

&lt;h4&gt;
  
  
  stack
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmuxiezwzlmidxurcfzea.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmuxiezwzlmidxurcfzea.png" alt="stack" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A stack (velha conhecida dos amantes de leetcode) é basicamente um bloco consecutivo de memória. Cada chamada de função dentro de uma thread em execução compartilha a mesma stack (AHA moment, dai o termo stack overflow, quando tentamos usar mais memória do que a alocada para aquela stack especifica), logo, alocar memória na stack é rápido e simples.&lt;/p&gt;

&lt;p&gt;Uma “variável” (mais precisamente um endereço especifico de memória) guarda o stack pointer. O stack pointer serve para rastrearmos o endereço de memória da última alocação do stack.&lt;/p&gt;

&lt;p&gt;A cada chamada de função estamos adicionando um stack frame na stack, que nada mais é que uma área delimitada na stack somente para aquela função, nessa carregamos as variáveis da função e seus argumentos, variáveis criadas dentro da função são guardadas na stack também. Assim que a função retorna, recalculamos o stack pointer e é desalocado a memória que aquela função estava ocupando, seguindo assim consecutivamente para todas as chamadas de funções.&lt;/p&gt;

&lt;h4&gt;
  
  
  heap
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm8y6xcjta0cyy200n29g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm8y6xcjta0cyy200n29g.png" alt="heap" width="283" height="297"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;O heap é uma área da memória que é dedicada a alocação de dados dinâmicos e é administrada pelo garbage collector. Outro ponto importante do heap é que ele é um espaço compartilhado entre threads / goroutines. Em termos de implementação, o heap é muito mais complexo, além de estar focado em dados de mais longo prazo, ou seja, enquanto tivermos um ponteiro apontado pra aquele dado ele vai continuar existindo no heap. É aqui que devemos ajudar o garbage collector também, seja não mandando muitos dados para ca, seja otimizando o que é mandado, quando necessário.&lt;/p&gt;

&lt;p&gt;Para um detalhamento mais baixo nível, tanto de heap quanto outras áreas de memória administradas pelo runtime do Go, indico este &lt;a href="https://medium.com/@ankur_anand/a-visual-guide-to-golang-memory-allocator-from-ground-up-e132258453ed" rel="noopener noreferrer"&gt;post&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beneficiando-se da stack e heap para performance
&lt;/h2&gt;

&lt;p&gt;Em termos de performance, queremos usar a stack o máximo possível, por ser mais eficiente e não onerar o garbage collector, quando utilizarmos o heap, queremos também utilizar de maneira mínima e inteligente, como por exemplo, utilizando buffers.&lt;/p&gt;

&lt;p&gt;Para alocar algo na stack, o compilador do Go precisa saber o tamanho exato do dado em tempo de compilação, quando olhamos para os tipos de valores em Go (primitivos, arrays e structs) todos eles tem uma coisa em comum: Sabemos exatamente quanto de memória é necessário para armazena-los (este é um dos motivos do porque o tamanho de um array em Go é considerado parte do seu tipo). Devemos abusar desses tipos para melhorar a performance. Um adendo é que ponteiros também são alocados na stack, porém os dados para os quais eles apontam podem estar na heap, então não se enquadram nesse grupo anterior.&lt;/p&gt;

&lt;p&gt;Já no caso do heap, ele é administrado pelo garbage collector. Quando um dado não tem mais um ponteiro apontando para ele, se torna “garbage” e pode ser desalocado na próxima varredura do garbage collector.&lt;/p&gt;

&lt;h3&gt;
  
  
  Porque é ruim utilizarmos o heap quando o assunto é performance?
&lt;/h3&gt;

&lt;p&gt;Primeiramente, o garbage collector precisa de um tempo precioso de CPU para ser executado, então paramos a execução do nosso programa temporariamente para que o garbage collector possa rodar. Este processo do garbage collector não é trivial, diversos algoritmos foram desenvolvidos ao longo do tempo, alguns focados em maior throughput (achar o maior número de garbage possível, desalocando o máximo de memória), outros em menor latência (acabar o processo de garbage collection o mais rápido possível, ao custo de ser menos eficiente em cada varredura). No caso específico do Go, seu garbage collector favorece mais latência baixa, então cada sweep (varredura) tende a rodar bem rápido, porém, se seu programa gera muito garbage, obviamente o garbage collector vai levar alguns sweeps para conseguir desalocar toda a memória ocupada por garbage, sendo assim bem menos eficiente. Para quem se interessar em saber mais desse processo do garbage collector, deixo aqui esse &lt;a href="https://go.dev/blog/ismmkeynote" rel="noopener noreferrer"&gt;post&lt;/a&gt; do blog do Go contando a historia do desenvolvimento do mesmo.&lt;/p&gt;

&lt;p&gt;O segundo problema tem mais a ver com detalhes de implementação do hardware, a RAM (random access memory) foi arquitetada para suportar acessos randômicos de áreas de memória, porém, a maneira mais rápida de acessar seus dados ainda é o sequencial. Um slice de inteiros possui todos os seus dados sequencias (um do lado do outro), tornando rápido a sua leitura, já um slice de ponteiros tem seus dados todos espalhados por toda a RAM, o que torna seu acesso bem mais devagar.&lt;/p&gt;

&lt;h2&gt;
  
  
  E como reduzimos o fardo do garbage collector?
&lt;/h2&gt;

&lt;p&gt;Com base nos conceitos apresentados anteriormente, podemos deduzir que devemos usar ponteiros com cuidado, uma vez que seus dados são armazenados no heap e ficarão espalhados na RAM de maneira randômica, então use ponteiros só quando for extremamente necessário, quando o foco for performance.&lt;/p&gt;

&lt;p&gt;Além do uso consciente de ponteiros, devemos focar ao máximo utilizar a stack, abuse de dados primitivos (números, booleanos, strings e runes), alocando assim o mínimo possível no heap, quanto menos garbage for pro heap mais eficiente os sweeps do garbage collector serão, diminuindo assim o seu fardo.&lt;/p&gt;

&lt;h2&gt;
  
  
  BONUS TIP:  Ferramentas para identificar o uso do heap
&lt;/h2&gt;

&lt;p&gt;A primeira ferramenta é o próprio &lt;code&gt;build&lt;/code&gt; do compilador do Go. Ao passarmos as gcflags &lt;code&gt;-m&lt;/code&gt; e &lt;code&gt;-l&lt;/code&gt; conseguimos fazer a &lt;em&gt;escape analysis do&lt;/em&gt; nosso código. Quando rodamos o build com essas flags teremos um output do compilador dando dicas de quando ele moveu algo pro heap, como no exemplo abaixo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./main.go:10:2: **moved to heap: res**
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Uma outra ferramenta que ajuda muito é o benchmark. Quando um programa está bem otimizado (não utilizando nada do heap), teremos o seguinte output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ go test -bench . -benchmem
BenchmarkStackIt-8  680439016  1.52 ns/op  0 B/op  0 allocs/op
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A coluna interessante é a de &lt;code&gt;0 allocs/op&lt;/code&gt; que indica que nenhuma alocação no heap foi executada. Se parar pra ler os benchmarks das libs famosas em Go vai começar a perceber que diversas delas possuem esse stat de 0 allocs (o que é bem impressionante 🤯🤯🤯).&lt;/p&gt;

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

&lt;p&gt;Entender a diferença entre heap e stack, assim como a maneira que o Go gerencia a memória, é crucial para otimizar a performance de suas aplicações. Utilizar a stack sempre que possível e ser cuidadoso com o uso de ponteiros pode reduzir significativamente a carga do garbage collector, resultando em programas mais eficientes e rápidos. Ferramentas como escape analysis e benchmarks ajudam a identificar e solucionar problemas de alocação de memória. Com essas práticas, você pode aproveitar ao máximo o desempenho que o Go tem a oferecer, criando aplicações robustas e eficientes. Lembre-se, pequenas otimizações no gerenciamento de memória podem fazer uma grande diferença na performance geral do seu sistema.&lt;/p&gt;

</description>
      <category>go</category>
      <category>performance</category>
      <category>learning</category>
    </item>
    <item>
      <title>OAuth em aplicações SPA / Mobile (PKCE extension)</title>
      <dc:creator>Erick Takeshi</dc:creator>
      <pubDate>Mon, 08 Jul 2024 03:25:46 +0000</pubDate>
      <link>https://forem.com/erick_tmr/oauth-em-aplicacoes-spa-mobile-pkce-extension-iao</link>
      <guid>https://forem.com/erick_tmr/oauth-em-aplicacoes-spa-mobile-pkce-extension-iao</guid>
      <description>&lt;p&gt;Em diversas implementações OAuth / OpenID Connect nos deparamos com o uso de clientes confidenciais (clientes registrados que possuem um par &lt;code&gt;client_id&lt;/code&gt; e &lt;code&gt;client_secret&lt;/code&gt;), porém, em aplicações client-side, como SPAs e apps mobile, é impossível garantir a confidencialidade do &lt;code&gt;client_secret&lt;/code&gt;. Portanto, torna-se necessário um meio seguro para obter o access token através de algum fluxo do OAuth.&lt;/p&gt;

&lt;p&gt;Para resolver esse problema de clientes públicos, entra em ação a &lt;a href="https://datatracker.ietf.org/doc/html/rfc7636" rel="noopener noreferrer"&gt;RFC 7636 Proof Key for Code Exchange by OAuth Public Clients&lt;/a&gt;, que adiciona uma extensão ao fluxo do authorization code, tornando a comunicação mais segura. É dessa RFC que quero aprofundar um pouco neste post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qual problema o PKCE tenta mitigar?
&lt;/h2&gt;

&lt;p&gt;Primeiramente, lê-se piquici (pixy), a sigla PKCE, haha.&lt;/p&gt;

&lt;p&gt;O PKCE mitiga um conhecido ataque do tipo &lt;code&gt;man in the midle&lt;/code&gt; , aplicado principalmente em aplicações mobile, segue um diagrama abaixo demostrando esse tipo de ataque.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwu5cstarlt22kj4fad66.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwu5cstarlt22kj4fad66.png" alt="authz-code-attack-mitm"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Explicando brevemente:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A aplicação cliente inicia o fluxo &lt;code&gt;authorization code&lt;/code&gt; utilizando um browser&lt;/li&gt;
&lt;li&gt;Browser faz o request para o endpoint /authorize do authorization server&lt;/li&gt;
&lt;li&gt;Browser recebe de volta a resposta do servidor com o &lt;code&gt;authorization code&lt;/code&gt; (code)&lt;/li&gt;
&lt;li&gt;Um app malicioso instalado no dispositivo cliente intercepta essa resposta que seria encaminhada do browser para a aplicação cliente (normalmente utilizando um custom URI scheme)&lt;/li&gt;
&lt;li&gt;O app malicioso, em posse do &lt;code&gt;authorization code&lt;/code&gt; , completa o fluxo trocando o code pelo access token e tendo acesso aos recursos protegidos autorizados pelo usuário.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Desta forma, um app malicioso que tenha infectado o dispositivo do cliente pode obter acesso a um access token interceptando code da resposta do request para o endpoint /authorize.&lt;/p&gt;

&lt;p&gt;Um ponto importante a se destacar é que devido a natureza dos clientes públicos de não conseguirem guardar de maneira segura suas credenciais, o fluxo do authorization code permite que estes obtenham o access token somente através da posse do code e do cliente_id da aplicação cliente. Este tipo de ataque se tornaria bem menos eficaz caso fosse possível autenticar a aplicação cliente através de seu client_secret.&lt;/p&gt;

&lt;h3&gt;
  
  
  OBS sobre custom URI schemes
&lt;/h3&gt;

&lt;p&gt;Para quem não é da área mobile ou front-end, custom URI schemes é uma forma de criar um protocolo custom para sua aplicação, assim como http:// ou https://, algo como myapp://.&lt;/p&gt;

&lt;p&gt;Assim, quando um usuário clicar ou for redirecionado para uma URL que utilize este protocolo, sua aplicação será inicializada com os parâmetros associados a URL.&lt;/p&gt;

&lt;p&gt;Para saber mais sobre indico este ótimo e breve artigo, &lt;a href="https://alirezahamid.medium.com/launching-applications-from-the-web-the-power-of-custom-url-schemes-4d9fa3e6cdbe" rel="noopener noreferrer"&gt;Launching Applications from the Web: The Power of Custom URL Schemes&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Como o PKCE mitiga este tipo de ataque?
&lt;/h2&gt;

&lt;p&gt;O PKCE mitiga este tipo de ataque introduzindo alguns parâmetros adicional nos requests envolvidos no fluxo do authorization code.&lt;/p&gt;

&lt;p&gt;No diagrama abaixo ilustramos essa nova dinâmica:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnuwzklpf93idtb0jmo6m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnuwzklpf93idtb0jmo6m.png" alt="pkce-athz-code-flow"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Explicando o novo fluxo:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Aplicação cliente cria um &lt;code&gt;code_verifier&lt;/code&gt;, uma string aleatória de alta entropia criptográfica&lt;/li&gt;
&lt;li&gt;Aplicação cliente deriva o &lt;code&gt;code_challenge&lt;/code&gt; do code_verifier aplicando algum algoritmo como SHA256, sendo este algoritmo denomiado &lt;code&gt;code_challenge_method&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Aplicação cliente encaminha o &lt;code&gt;code_challenge&lt;/code&gt; e o &lt;code&gt;code_challenge_method&lt;/code&gt; , junto dos demais parâmetros, no request para o /authorize&lt;/li&gt;
&lt;li&gt;O authorization server guarda o code_challenge e o code_challenge_method, associando-os ao authorization code emitido na resposta do /authorize&lt;/li&gt;
&lt;li&gt;No request para o /token a aplicação cliente envia junto do code o &lt;code&gt;code_verifier&lt;/code&gt; gerado no passo 1, além dos demais parâmetros necessários&lt;/li&gt;
&lt;li&gt;Authorization server vai aplicar as transformações no code_verifier baseado no code_challenge e code_challenge_method associados com o authorization code recebido&lt;/li&gt;
&lt;li&gt;Caso a verificação seja bem sucedida, o authorization server retorna o access code para a aplicação cliente&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Então, com a introdução destes novos parâmetros, mesmo que algum app malicioso seja capaz de interceptar o authorization code, este não será capaz de trocar o code por um access token no endpoint /token por não saber o &lt;code&gt;code_verifier&lt;/code&gt; e o &lt;code&gt;code_challenge_method&lt;/code&gt; utilizado na comunicação com o authorization server. &lt;/p&gt;

&lt;h2&gt;
  
  
  Considerações de segurança
&lt;/h2&gt;

&lt;p&gt;A extensão do PKCE parte do princípio que o code verifier pode ser mantido em segredo do atacante (app malicioso) e que é praticamente impossível de ser adivinhado ou derivado pelo atacante.&lt;/p&gt;

&lt;p&gt;Para que o code verifier atinja o requisito satisfatório de aleatoriedade criptográfica, sugere-se utilizar pelo menos 256 bits de entropia.&lt;/p&gt;

&lt;p&gt;Pessoalmente, sugiro que utilize bibliotecas open-source confiáveis para gerar o code_challege e verifier, uma vez que são padrões conhecidos. Recorrer a experiência e poder do open-source é sempre bem vindo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Concluindo
&lt;/h2&gt;

&lt;p&gt;O PKCE resolve o problema de clientes públicos, como SPAs e Mobile apps, em obter de maneira segura o access token, usufruindo assim dos benefícios do OAuth sem precisar comprometer sua segurança, então lembre-se sempre de utilizar essa extensão ao implementar clientes OAuth públicos.&lt;/p&gt;

&lt;p&gt;Para saber mais sobre OAuth e OpenID Connect, deixo aqui a minha coletânea de posts sobre o tema.&lt;/p&gt;

</description>
      <category>security</category>
      <category>webdev</category>
      <category>mobile</category>
      <category>oauth</category>
    </item>
    <item>
      <title>Implementando seu próprio link tree</title>
      <dc:creator>Erick Takeshi</dc:creator>
      <pubDate>Sun, 09 Jun 2024 01:06:30 +0000</pubDate>
      <link>https://forem.com/erick_tmr/implementando-seu-proprio-link-tree-2hij</link>
      <guid>https://forem.com/erick_tmr/implementando-seu-proprio-link-tree-2hij</guid>
      <description>&lt;p&gt;Os tempos mudaram. Antigamente, bastava ter um perfil atualizado no LinkedIn para receber várias propostas de emprego como desenvolvedor de software. Nosso mercado mudou da água pro vinho pós colapso da pandemia. As empresas estão muito mais exigentes com relação aos seus candidatos e a concorrência está cada vez mais intensa.&lt;/p&gt;

&lt;p&gt;Não quero assustar ninguém, mas sim ressaltar a importância da &lt;strong&gt;presença online&lt;/strong&gt;, não só como “marketing” pessoal, mas também como portfólio mesmo. Ter algo para mostrar, como um blog, um site próprio, repositórios profissionais, é quase que requerimento, não um diferencial.&lt;/p&gt;

&lt;p&gt;Com isso em mente, decidi criar uma página pessoal no formato &lt;em&gt;link tree&lt;/em&gt; para aprender um pouco sobre serviços de CDN, deploy contínuo (CI/CD) e também permissionamento cross plataforma (OAuth, OIDC), e é sobre esse projetinho que quero conversar um pouco.&lt;/p&gt;

&lt;h2&gt;
  
  
  Arquitetura e stack
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftyeaxqw2odybgl0wf954.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftyeaxqw2odybgl0wf954.png" alt="project-stack" width="757" height="273"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;O projeto em si é simples, pois o foco era aprender mais sobre certas tecnologias, mas mesmo assim vale destacar um pouco cada caixinha do diagrama acima.&lt;/p&gt;

&lt;h3&gt;
  
  
  Componentes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Hospedagem de arquivos estáticos no AWS S3&lt;/li&gt;
&lt;li&gt;Repositório público no Github (&lt;a href="https://github.com/erick-tmr/link-in-bio-self-profile"&gt;https://github.com/erick-tmr/link-in-bio-self-profile&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Github Actions como pipeline de deploy&lt;/li&gt;
&lt;li&gt;Cloudflare para CDN / Proxy Reverso&lt;/li&gt;
&lt;li&gt;Autenticação via OIDC com tokens short lived para uso no AWS CLI&lt;/li&gt;
&lt;li&gt;Gravatar como CMS&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Sobre o código
&lt;/h3&gt;

&lt;p&gt;Utilizei como base este &lt;a href="https://github.com/mackenly/quickbiolinks/blob/master/templates/simple-gravatar-dynamic/README.md"&gt;template&lt;/a&gt;, que é open source e já possui uma integração interessante de CMS (Gravatar). Do template, acabei não mudando muitas coisas, a mudança mais notável foi na utilização de modules e import maps, uma funcionalidade nativa de navegadores modernos, que acabou englobando uma fatia dos bundlers como Webpack, praticamente tornando dispensável sua utilização em projetos mais simples, como este.&lt;/p&gt;

&lt;p&gt;Quem der uma olhada no repositório deste projeto vai ver que utilizei uma lib externa para fazer o MD5 de uma string, tudo com import maps, direto do CDN da NPM.&lt;/p&gt;

&lt;p&gt;Pra quem ainda não experimentou, diria que é uma inovação bem legal e que vale a pena dar uma olhada, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap"&gt;link&lt;/a&gt; para as docs da MDN.&lt;/p&gt;

&lt;h2&gt;
  
  
  CI / CD e automação de deploy
&lt;/h2&gt;

&lt;p&gt;Para o CI / CD, optei pelo Github Actions, por ser gratuito para projetos open source e ser bem prático.&lt;/p&gt;

&lt;p&gt;O pipeline é simples, utilizando uma action da própria AWS para fazer a autenticação, é gerado um token de curta duração para assumir uma Role do IAM já pré configurada com as permissões necessárias para poder fazer o sync com um bucket do S3.&lt;/p&gt;

&lt;p&gt;Trabalho no time de segurança da empresa onde estou atualmente, lidando diariamente com mTLS, OAuth, OpenID Connect, etc. Acabei testando essa solução mais por questões de benchmark, mas foi um aprendizado bem interessante, particularmente acho bem legal poder trabalhar num &lt;em&gt;business context&lt;/em&gt; que, de certa forma, é agnóstico da empresa que você está no momento.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hospedagem S3
&lt;/h2&gt;

&lt;p&gt;Como o site é simples e consiste somente de alguns arquivos estáticos, optei pelo S3 para armazenar e distribuir os arquivos. Para quem não conhece, o S3 possui uma funcionalidade nativa para transformar determinado bucket em um site estático, expondo assim uma URL pública para que as pessoas tenham acesso ao bucket, tendo também um free tier bem legal.&lt;/p&gt;

&lt;p&gt;Um adendo aqui foi a parte de permissionamento, sofri um pouco para conseguir configurar as policies necessárias para que o bucket se tornasse publico de fato, devido a querer restringir o acesso ao S3 somente a Cloudflare, que foi o serviço de CDN escolhido, mas depois de bater a cabeça um pouco acabou dando certo.&lt;/p&gt;

&lt;h2&gt;
  
  
  CDN / Proxy Reverso
&lt;/h2&gt;

&lt;p&gt;Aqui eu utilizei a Cloudflare mais por conta de querer experimentar algumas features que eu iria utilizar no meu trampo mesmo, mas talvez se não tivesse esse requisito, eu teria utilizado o próprio CloudFront da AWS.&lt;/p&gt;

&lt;p&gt;Poderia simplesmente não utilizar nenhum CDN e apontar meu DNS diretamente para a URL pública do meu bucket, porém, existe uma restrição do S3 em que não é possível utilizar TLS / HTTPS, sendo assim, pra ter uma proteção mais legal, o uso de algum CDN na frente se torna indispensável.&lt;/p&gt;

&lt;p&gt;A Cloudflare proporciona não só o serviço de CDN como proteção a ataques DDoS, certificados SSL, cache inteligente e mais, tudo no plano gratuito, o que é algo bem legal. A feature que eu gostaria de testar mesmo, o mTLS, acabou não rolando, o avoado que vôs fala não verificou antes que esta feature está presente somente em planos enterprise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Concluindo
&lt;/h2&gt;

&lt;p&gt;Sobre o projeto, é isso! No futuro, pretendo escrever um post mais detalhado, com cara de tutorial, ensinando passo a passo cada etapa desse processo. Se você ficou interessado e quer ver essa parte dois, deixe um comentário ou mande um sinal de fumaça haha.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>beginners</category>
      <category>portfolio</category>
    </item>
    <item>
      <title>Instruction Set Architecture, a linguagem das máquinas</title>
      <dc:creator>Erick Takeshi</dc:creator>
      <pubDate>Wed, 29 May 2024 22:09:36 +0000</pubDate>
      <link>https://forem.com/erick_tmr/instruction-set-architecture-a-linguagem-das-maquinas-4o8d</link>
      <guid>https://forem.com/erick_tmr/instruction-set-architecture-a-linguagem-das-maquinas-4o8d</guid>
      <description>&lt;p&gt;Para se comunicar com um computador precisamos falar a sua língua, ao vocabulário desta “linguagem” é conhecido como &lt;em&gt;Instruction Set.&lt;/em&gt; Para **os programadores de plantão, podemos dizer que a ISA de um hardware é a interface deste para com o software, ou, simplificando, sua API.&lt;/p&gt;

&lt;p&gt;Já abordei um pouco sobre como computadores funcionam e como ocorre a comunicação entre a nossa linguagem de programação e o hardware em um &lt;a href="https://dev.to/erick_tmr/como-um-computador-funciona-4me9"&gt;post&lt;/a&gt; anterior, mas vou recapitular brevemente.&lt;/p&gt;

&lt;h2&gt;
  
  
  Como &lt;em&gt;ifs&lt;/em&gt;, &lt;em&gt;fors&lt;/em&gt; e &lt;em&gt;vars&lt;/em&gt; viram binários que o computador entende?
&lt;/h2&gt;

&lt;p&gt;Como dito anteriormente, o computador entende somente &lt;em&gt;instructions,&lt;/em&gt; ou seja, precisamos utilizar a ISA para operar um computador. As &lt;em&gt;instructions&lt;/em&gt; são constituídas por sequencias de números binários que possuem significado para o computador.&lt;/p&gt;

&lt;p&gt;Utilizar números binários direto para se comunicar com o computador é uma tarefa complexa e tediosa, ou seja, um prato cheio para que um programador torne esta tarefa mais simples, então, foi criado um programa que ajuda a programar, os chamados &lt;code&gt;assemblers&lt;/code&gt;, que traduziam linguagem escrita e entendida por seres humanos em linguagem de máquina (instructions).&lt;/p&gt;

&lt;p&gt;Apesar do &lt;code&gt;assembly&lt;/code&gt; já ser uma simplificação absurda em comparação com as &lt;em&gt;instructions&lt;/em&gt; cruas, ainda era muito semelhante a forma de pensar e agir da máquina e não do ser humano, sendo assim foram criadas as linguagens de programação de mais alto nível, como C, C++, Java e afins.&lt;/p&gt;

&lt;p&gt;As primeiras linguagens funcionavam como uma abstração acima da camada do &lt;code&gt;assembly&lt;/code&gt;, onde você tinha um programa que transformava os comandos da linguagem em comandos &lt;code&gt;assembly&lt;/code&gt; , esse programa é chamado de compilador.&lt;/p&gt;

&lt;p&gt;O compilador torna acessível a comunicação com o computador, fazendo com que consigamos modelar de maneira mais próxima ao nosso pensamento o que gostaríamos que a máquina executasse.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// o seguinte programa em C só é possível de ser interpretado por conta dos compiladores

swap(size_t v[], size_t k) {
    size_t temp;
    temp = v[k];
    v[k] = v[k+1];
    v[k+1] = temp;
}

// traduzido para assembly pelo compilador
swap:
    slli x6, x11, 3
    add  x6, x10, x6
    lw   x5, 0(x6)
    lw   x7, 4(x6)
    sw   x7, 0(x6)
    sw   x5, 4(x6)
    jalr x0, 0(x1)

// traduzido para binários
00000000001101011001001100010011
00000000011001010000001100110011
00000000000000110011001010000011
00000000100000110011001110000011
00000000011100110011000000100011
00000000010100110011010000100011
00000000000000001000000001100111
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Operações aritméticas
&lt;/h2&gt;

&lt;p&gt;Qualquer computador precisa saber trabalhar com operações aritméticas, sendo assim, as instruções aritméticas formam a base de qualquer ISA.&lt;/p&gt;

&lt;p&gt;Analisando a arquitetura RISC-V e sua linguagem assembly, temos 3 instruções aritméticas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add (Soma, utiliza 3 registradores)&lt;/li&gt;
&lt;li&gt;Subtract (Subtração, utiliza 3 registradores)&lt;/li&gt;
&lt;li&gt;Add immediate (Soma com constantes, utiliza 2 registradores)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Operações aritméticas em hardware acontecem somente em lugares específicos, esses lugares são chamados de registradores.&lt;/p&gt;

&lt;p&gt;Continuando com a arquitetura RISC-V, ela possui 32 registradores de 32 bits de tamanho, esse agrupamento de 32 bits acontece com tanta frequência que chamamos de &lt;em&gt;word&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Operações de memória
&lt;/h2&gt;

&lt;p&gt;Como vimos, a arquitetura RISC-V possui 32 registradores de 32 bits cada, porém sabemos das linguagens de programação que podemos ter tipos de tamanho variável, ou até tipos compostos como arrays. Dada essa necessidade precisamos de instruções que consigam mover dados dos registradores para a memória do computador, e vice versa.&lt;/p&gt;

&lt;p&gt;As instruções que movem dados entre registradores e memória damos o nome de &lt;em&gt;data transfer instructions&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Em RISC-V temos duas instruções de transferência de dados:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Load (Copia um word de um certo endereço da memória para um registrador)&lt;/li&gt;
&lt;li&gt;Store (Copia um word de um registrador para um certo endereço da memória)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A memória do computador nada mais é do que um grande array, onde os índices são os seus endereços. Em RISC-V, como trabalhamos primariamente com words (agrupamentos de 32 bits ou 4 bytes), o endereço de um word coincide com o endereço de um de seus bytes, words que são vizinhos estão separados por 4 endereços, consequentemente.&lt;/p&gt;

&lt;p&gt;Computadores são divididos entre aqueles que usam o endereço do byte mais à esquerda ("big-endian") e aqueles que usam o endereço do byte mais à direita ("little-endian"), a esse conceito damos o nome de &lt;em&gt;endiannes&lt;/em&gt;. RISC-V utiliza o “liitle-endian”, ou seja, o endereço de um word equivale ao endereço do seu byte mais a direita.&lt;/p&gt;

&lt;h2&gt;
  
  
  Representando instruções no computador
&lt;/h2&gt;

&lt;p&gt;Instruções da ISA são mantidas no hardware através de uma série de sinais eletrônicos de baixa e alta voltagem, esses sinais podem ser representados em números, como 0 e 1, ou seja, bits.&lt;/p&gt;

&lt;p&gt;Cada componente de uma instrução pode ser considerada como um número individual, agrupando esses números lado a lado temos a representação da instrução. A essa forma de agrupamento de bits damos o nome de “instruction format”.&lt;/p&gt;

&lt;p&gt;Dando como exemplo uma instrução de soma do RISC-V:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;add x9, x20, x21

Representação decimal:
0  21  20  0  9  51

Cada segmento é chamado de "field"
Os segmentos 1, 4 e 6 (contendo os números 0, 0 e 51) coletivamente dizem para 
o computador RISC-V que essa é uma instrução de soma.
O segmento 2 diz qual é o ultimo registrador da operação de soma (um dos "somandos")
O segmento 3 diz o outro registrador envolvido na soma
O segmento 5 contém o registrador que vai receber o resultado da operação

Representação binária:
|  0000000  |  10101  |  10100  |  000  |  01001  |  0110011  |
   7 bits     5 bits    5 bits   3 bits   5 bits     7 bits 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para distinguir a representação numérica da linguagem assembly, chamamos ela de &lt;em&gt;machine language&lt;/em&gt;, uma sequência de instruções é chamada de &lt;em&gt;machine code&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finalizando
&lt;/h2&gt;

&lt;p&gt;Ao explorar como instruções simples, como operações aritméticas e transferências de dados, são representadas em binário e executadas pelo hardware, podemos apreciar a complexidade e a elegância dos sistemas computacionais. Essa compreensão não só melhora nossas habilidades como programadores, mas também nos permite otimizar e depurar nosso código de maneira mais eficiente.&lt;/p&gt;

&lt;p&gt;A evolução das linguagens de programação, desde o assembly até as linguagens de alto nível, reflete o constante esforço da comunidade de tecnologia em tornar a interação com computadores mais acessível e intuitiva. No entanto, a base de toda essa evolução permanece na ISA, que continua a ser o alicerce sobre o qual construímos nossas aplicações.&lt;/p&gt;

&lt;p&gt;Apesar de tudo isso, não se sinta pressionado a dominar esses conceitos profundamente. Nós, programadores, trabalhamos diretamente com as linguagens de programação, que foram criadas para abstrair essa complexidade e nos ajudar a ser mais produtivos. Conheço vários ótimos programadores que não dominam esses conceitos e estão se saindo muito bem em suas carreiras. Aprofundar-se em ISA é uma escolha que pode enriquecer sua compreensão e habilidade técnica, mas não é um requisito para ser um excelente programador.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>architecture</category>
      <category>hardware</category>
    </item>
    <item>
      <title>Estrutura de projetos Go</title>
      <dc:creator>Erick Takeshi</dc:creator>
      <pubDate>Fri, 17 May 2024 23:16:47 +0000</pubDate>
      <link>https://forem.com/erick_tmr/estrutura-de-projetos-go-4o7l</link>
      <guid>https://forem.com/erick_tmr/estrutura-de-projetos-go-4o7l</guid>
      <description>&lt;p&gt;Quando estamos iniciando um novo projeto em uma nova linguagem, surgem algumas perguntas essenciais:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Qual estrutura devo utilizar para este projeto?"&lt;/li&gt;
&lt;li&gt;"Existe alguma convenção própria da linguagem?"&lt;/li&gt;
&lt;li&gt;"Há um padrão amplamente aceito pela comunidade?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Este artigo busca responder a essas perguntas no contexto da linguagem Go.&lt;/p&gt;

&lt;p&gt;A seguir, compilei as práticas mais comuns adotadas pela comunidade, incluindo padrões reforçados pelo compilador da linguagem.&lt;/p&gt;

&lt;h2&gt;
  
  
  /cmd
&lt;/h2&gt;

&lt;p&gt;Dentro deste diretório devemos criar subdiretórios que representaram cada executável do nosso projeto.&lt;/p&gt;

&lt;p&gt;Uma conversão é de não colocarmos muito código dentro deste diretórios ou no arquivo principal. É comum ter uma pequena função &lt;code&gt;main&lt;/code&gt; que importa e executa código dos outros diretórios como &lt;code&gt;internal&lt;/code&gt; e &lt;code&gt;pkg&lt;/code&gt; .&lt;/p&gt;

&lt;p&gt;Exemplo de uso no &lt;a href="https://github.com/kubernetes/kubernetes/tree/master/cmd"&gt;Kubernetes&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  /internal
&lt;/h2&gt;

&lt;p&gt;Diretório utilizado para código privado, que não é feito para ser utilizado por outros projetos. Esse padrão é aplicado pelo próprio compilador do Go.&lt;/p&gt;

&lt;p&gt;Um adendo é que não estamos limitados somente ao top-level /internal, podemos ter quantos /internal quisermos em qualquer nível de camada da árvore do projeto.&lt;/p&gt;

&lt;p&gt;A estrutura dentro do /internal é livre, podemos adicionar o que quisermos.&lt;/p&gt;

&lt;h2&gt;
  
  
  /pkg
&lt;/h2&gt;

&lt;p&gt;Diretório utilizado para código que pode ser compartilhado com outros projetos, podendo considerar código nesse diretório como público. É uma forma explícita de comunicar que o código aqui dentro é seguro de ser utilizado por terceiros.&lt;/p&gt;

&lt;p&gt;Embora seja um padrão popular em diversos projetos (&lt;a href="https://github.com/containerd/containerd/tree/main/pkg"&gt;containerd&lt;/a&gt;, &lt;a href="https://github.com/istio/istio/tree/master/pkg"&gt;istio&lt;/a&gt;, &lt;a href="https://github.com/grafana/grafana/tree/master/pkg"&gt;grafana&lt;/a&gt;, etc), ele não é um padrão universalmente aceito, e alguns membros da comunidade Go até não o recomendam, como podemos ver na seguinte &lt;a href="https://github.com/golang-standards/project-layout/issues/10"&gt;discussão&lt;/a&gt; sobre o tema.&lt;/p&gt;

&lt;h2&gt;
  
  
  /vendor
&lt;/h2&gt;

&lt;p&gt;Diretório dedicado a dependências da aplicação, normalmente é administrado por ferramentas como o &lt;code&gt;go mod&lt;/code&gt; . O comando &lt;code&gt;go mod vendor&lt;/code&gt; vai gerar este diretório automaticamente.&lt;/p&gt;

&lt;p&gt;Uma observação importante é a de que não devemos commitar este diretório em nosso repositório Git, por exemplo, principalmente se estivermos desenvolvendo uma biblioteca pública.&lt;/p&gt;

&lt;h2&gt;
  
  
  Outras divisões relevantes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  /api
&lt;/h3&gt;

&lt;p&gt;Guardar specs OpenAPI, json schema, protocol buffers defs, etc&lt;/p&gt;

&lt;h3&gt;
  
  
  /web ou /ui
&lt;/h3&gt;

&lt;p&gt;Guardar arquivos estáticos da web, como arquivos HTML, CSS e JS, templates SSR (server side rendered) e SPAs.&lt;/p&gt;

&lt;h3&gt;
  
  
  /deployments ou /deploy
&lt;/h3&gt;

&lt;p&gt;Arquivos de orquestração de containers, configuração de IaaS ou PaaS, etc (docker compose, kubernetes, terraform)&lt;/p&gt;

&lt;h3&gt;
  
  
  /test
&lt;/h3&gt;

&lt;p&gt;Arquivos para teste automatizado e dados para testes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adendo final
&lt;/h2&gt;

&lt;p&gt;É importante ressaltar que esta estrutura não é  exclui a aplicação de qualquer arquitetura que seja, como uma Layered baseada em DDD ou Clean Arch, ou Event Driven.&lt;/p&gt;

&lt;p&gt;A organização de pastas de um projeto é agnóstica à arquitetura implementada, embora muitas vezes possa refletir a mesma.&lt;/p&gt;

&lt;p&gt;Com estas sugestões de organização, você pode iniciar seu projeto Go de maneira mais estruturada e em conformidade com as práticas aceitas pela comunidade.&lt;/p&gt;

</description>
      <category>go</category>
      <category>learning</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Como um computador funciona?</title>
      <dc:creator>Erick Takeshi</dc:creator>
      <pubDate>Thu, 21 Mar 2024 21:17:20 +0000</pubDate>
      <link>https://forem.com/erick_tmr/como-um-computador-funciona-4me9</link>
      <guid>https://forem.com/erick_tmr/como-um-computador-funciona-4me9</guid>
      <description>&lt;p&gt;Você, programador, sabe como um computador funciona?&lt;/p&gt;

&lt;p&gt;Dizer que é uma máquina que sabe interpretar números binários está até que relativamente correto, porém você sabe elaborar mais um pouco sobre isto? Tudo bem não saber a resposta pra esta pergunta, mesmo que num alto nível, afinal, no nosso dia a dia como desenvolvedores estamos trabalhando em uma camada muito acima, na hierarquia do computador, ou seja, extremamente distantes do “metal”.&lt;/p&gt;

&lt;p&gt;Eu mesmo tinha uma noção, por ter feito faculdade de engenharia elétrica com ênfase em computação, porém, só fui me aprofundar no tópico mais recentemente, quando me interessei em aprender mais sobre emuladores.&lt;/p&gt;

&lt;p&gt;Um último disclaimer é que, não se sinta pressionado em saber essa resposta também! Afinal, posso afirmar com certeza que você pode ser um ótimo desenvolvedor sem fazer o mínimo de noção de como uma computador está organizado, tendo uma noção de conceitos mais alto nível como memória, CPU, threads, I/O e etc. Nosso papel como desenvolvedores é o de nos especializarmos em algoritmos, concorrência e paralelismo e otimização do uso de I/O, podendo abstrair hoje 100% da parte do “metal”, principalmente na era da cloud.&lt;/p&gt;

&lt;h2&gt;
  
  
  Abstrações e magias que conseguimos com software
&lt;/h2&gt;

&lt;p&gt;De uma perspectiva mais simplista, podemos dividir a organização de um computador da seguinte forma:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fefsuayn7wcy0fvcn7tb9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fefsuayn7wcy0fvcn7tb9.png" alt="Computer Layers"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sendo a camada mais externa, o software que nós programadores escrevemos, usando linguagens de programação como C, C++, Java, Ruby, Go, etc.&lt;/p&gt;

&lt;p&gt;A camada intermediária seria a interface entre nosso software com o hardware de fato, sendo dois grandes exemplos de software de interface os compiladores e os sistemas operacionais.&lt;/p&gt;

&lt;p&gt;O papel do sistema operacional normalmente é o de prover diversas APIs ao hardware assim como mandar funções de supervisionamento.&lt;/p&gt;

&lt;p&gt;Alguns exemplos de funções importantes do OS (&lt;em&gt;operating system&lt;/em&gt;, ou sistema operacional):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lidar com I/O&lt;/li&gt;
&lt;li&gt;Alocação de memória e armazenamento (disco)&lt;/li&gt;
&lt;li&gt;Prover uma interface segura para que diferentes aplicações possam utilizar os recursos do computador de maneira simultânea e harmoniosa&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Compiladores executam uma tarefa vital também para o funcionamento dos programas, eles “traduzem” programas escritos em linguagens mais alto nível (como C ou Java) em instruções que o hardware consegue executar, chamadas de &lt;em&gt;machine language&lt;/em&gt; (ou linguagem de máquina). Alguns compiladores conseguem executar uma tradução direta, outros utilizam um passo intermediário, onde transformam o código alto nível em um de mais baixo nível chamado de &lt;em&gt;assembly&lt;/em&gt;, que nada mais é do que uma linguagem que abstrai um pouco a linguagem de máquina (bits) em instruções mais fáceis de um humano entender, porém estando bem mais próximo do jeito que computadores de fato funcionam. O software que consegue converter assembly em linguagem de máquina é normalmente chamado de &lt;em&gt;assembler&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Olhando embaixo do capô
&lt;/h2&gt;

&lt;p&gt;Após analisar um pouquinho como as camadas de software interagem e compunham nossos programas, vamos abaixar um degrau e olhar como a parte de hardware funciona.&lt;/p&gt;

&lt;p&gt;Existem 5 principais categorias de componentes de um computador, qualquer componente, seja ele antigo ou moderno, pode ser categorizado sob essas cinco categorias. Elas são input (entrada), output (saída), memória, datapath (caminho de dados) e controle, sendo datapath e controle normalmente agrupados como processadores.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2v5embsg8pzxue2nvydw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2v5embsg8pzxue2nvydw.png" alt="Computer Components"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  I / O
&lt;/h3&gt;

&lt;p&gt;Dispositivos de I/O são os dispositivos que geram dados de entrada e saída no computador, normalmente são os que estamos mais familiarizados, exemplos de dispositivos de I/O são monitores, teclados, mouses, placas de rede, bluetooth, câmeras, microfones, etc.&lt;/p&gt;

&lt;p&gt;Não entraremos tanto em detalhes pois cada dispositivo diferente de I/O tem seu propósito específico, além disso, já estamos mais familiarizados com seus funcionamentos.&lt;/p&gt;

&lt;h3&gt;
  
  
  Memória
&lt;/h3&gt;

&lt;p&gt;Memória é basicamente o componente responsável por guardar instruções de programas e dados do computador.&lt;br&gt;
Existem algumas categorias de memórias, sendo a mais comumente conhecida a DRAM (dynamic random access memory). RAM (random access memory) é um termo utilizado para denominar unidades de armazenamento que levam o mesmo tempo para acessar qualquer parte desta unidade, ou seja, não importa em qual parte o dado se encontra, fisicamente, ele pode ser acessado no mesmo intervalo de tempo.&lt;/p&gt;

&lt;p&gt;Dentro do processador encontramos um outro tipo de memória chamada de Cache, uma memória extremamente rápida, que é utilizada como um buffer da DRAM (um buffer nada mais é do que um espaço onde dados que são mais frequentemente utilizados são guardados, sendo este um local de maior performance, economizado outros recursos e tornando o computador mais eficiente como um todo). A tecnologia utilizada para construir um Cache do processador é chamada de SRAM (static random access memory), uma tecnologia muito mais rápida do que a DRAM, porém mais cara.&lt;/p&gt;

&lt;p&gt;Tanto a DRAM quanto a SRAM são tecnologias de memória voláteis, ou seja, assim que a energia do computador é cortada elas são completamente apagadas. Tendo esta característica em mente, surgiu a necessidade de termos um espaço diferente no computador para armazenarmos dados persistentes, assim surgiram os discos magnéticos, uma tecnologia de armazenamento de longo prazo, onde os dados ficam gravados mesmo quando não há energia no computador. Posteriormente, principalmente com o advento de dispositivos mobiles, um segundo tipo de tecnologia de armazenamento de local prazo surgiu como alternativa, o flash memory, sendo bem utilizado por conta de seu tamanho físico e custo por bits.&lt;/p&gt;

&lt;p&gt;Na hierarquia de memória, chamamos DRAM / SRAM de memória primária, enquanto flash memory e discos magnéticos são categorizados como memória secundária.&lt;/p&gt;

&lt;h3&gt;
  
  
  Processador
&lt;/h3&gt;

&lt;p&gt;O processador ou CPU é um componente complexo dos computadores, formado por diversas sub-máquinas, ele é responsável por buscar instruções da memória, decodificá-las, executar as operações (contas aritméticas, acesso a memória ou controlar outras parts do sistema) e escrever os resultados de volta, seja na memória ou em seus registradores. Todos essas passos são executados em um loop ininterrupto.&lt;/p&gt;

&lt;p&gt;Alguns componentes notórios da CPU são seus registradores, a ALU (arithmetic logic unit) e suas unidades de controle.&lt;/p&gt;

&lt;p&gt;Registradores são unidades de memória do processador, totalmente separadas da RAM, onde são guardadas dados intermediários, frutos de algum processo de cálculo, registradores de inputs, que guardam dados da RAM e de outputs, que enviam resultados de volta pra RAM (normalmente como buffers).&lt;/p&gt;

&lt;p&gt;ALU é a unidade lógica aritmética, nada mais é do que um conjunto de maquinas simples que tem o papel de executar operações aritméticas, cada uma especializada em um tipo de operação.&lt;/p&gt;

&lt;p&gt;A unidade de controle é responsável por ler instruções de um programa da RAM, decodificá-las e passa-las para a ALU ou pra outro lugar para serem executadas (como gravar algo em disco ou executar algo em I / O). Após este passo, ela atualiza o ponteiro do programa (PC, program counter), que mantém guardado a informação da localização da próxima instrução a ser executada. As instruções são lidas sequencialmente ou executando pulos (ou seja, indo para um lugar não sequencial baseado em alguma instrução prévia).&lt;/p&gt;

&lt;h2&gt;
  
  
  A interface entre hardware e o software
&lt;/h2&gt;

&lt;p&gt;Um dos princípios fundamentais da arquitetura de computadores é que abstrações simplificam o design. Isso significa que ao utilizar abstrações, programadores e arquitetos podem se comunicar de forma mais eficaz. Um dos avanços significativos na área da computação foi ocultar os detalhes de implementação do hardware, expondo apenas um modelo simples e padronizado para que os programadores pudessem escrever seus programas.&lt;/p&gt;

&lt;p&gt;Uma das mais importantes abstrações é a interface entre hardware e software, que chamamos de instruction set architecture (ISA). A ISA é formada por uma série de instruções que o computador é capaz de executar, como operações aritméticas, I / O e etc.&lt;/p&gt;

&lt;p&gt;Tipicamente, temos o sistema operacional que encapsula detalhes de como fazer I / O, alocar memória e outras funções mais baixo nível, para que programadores não tenham que se preocupar com isto. A combinação  de instruções básicas e a API fornecida pelo sistema operacional é chamada de application binary interface.&lt;/p&gt;

&lt;h3&gt;
  
  
  Qual a relação entre ISA e assembly?
&lt;/h3&gt;

&lt;p&gt;A ISA caracteriza o que um hardware pode fazer, ou seja, inclui todas as instruções que uma CPU pode executar, endereço de seus registradores, o modelo de memória utilizado, como instruções são decodificadas, etc. A linguagem assembly é uma linguagem de programação que corresponde muito proximamente as instruções da ISA (Lembrando que as instruções da ISA normalmente são codificadas como um binário que representa determinada instrução específica).&lt;/p&gt;

&lt;p&gt;A relação entre a ISA e o assembly é que o assembly proporciona uma representação passível de compreensão por humanos da ISA de determinado hardware.&lt;/p&gt;

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

&lt;p&gt;Em resumo, podemos dizer que a arquitetura de computadores é um campo fascinante que abrange desde os conceitos mais abstratos, como a forma como os programas são escritos e interpretados pelos computadores, até os detalhes mais concretos, como o funcionamento interno dos componentes de hardware. Embora muitos programadores possam trabalhar sem a necessidade de entender profundamente esses detalhes, ter uma compreensão básica de como um computador funciona pode ser extremamente útil para compreender melhor o funcionamento dos programas que escrevemos e para tomar decisões de design mais informadas.&lt;/p&gt;

&lt;p&gt;Referências&lt;br&gt;
Imagens tiradas do livro &lt;a href="https://www.amazon.com/Computer-Organization-Design-RISC-V-Architecture/dp/0128203315" rel="noopener noreferrer"&gt;Computer Organization and Design&lt;/a&gt;&lt;/p&gt;

</description>
      <category>hardware</category>
      <category>computerscience</category>
      <category>computerarchitecture</category>
    </item>
    <item>
      <title>Injeção de dependência em Go</title>
      <dc:creator>Erick Takeshi</dc:creator>
      <pubDate>Wed, 28 Feb 2024 01:37:29 +0000</pubDate>
      <link>https://forem.com/erick_tmr/injecao-de-dependencia-em-go-49k6</link>
      <guid>https://forem.com/erick_tmr/injecao-de-dependencia-em-go-49k6</guid>
      <description>&lt;p&gt;Software é uma arte em constante mudança e evolução.&lt;/p&gt;

&lt;p&gt;Como programadores, sabemos que certamente seremos requisitados a mudarmos nosso código, seja implementando uma &lt;em&gt;feature&lt;/em&gt; nova, corrigindo um &lt;em&gt;bug&lt;/em&gt; ou rodando em um ambiente novo. Tendo isso em mente, uma das maiores características que devemos mirar para que nosso código tenha é a de ser fácil de modificar.&lt;/p&gt;

&lt;p&gt;Engenheiros de software estão sempre repetindo palavras como desacoplamento, que significa que diferentes partes do software podem ser modificadas sem que afetem outras partes.&lt;/p&gt;

&lt;p&gt;Uma das técnicas mais famosas, quando se trata de alcançar desacoplamento, é a injeção de dependência. A injeção de dependência é um conceito que dita que seu código deve ser explícito ao declarar suas dependências, sendo este conceito cunhado pela lenda Uncle Bob (Robert Martin), no seu famigerado artigo “&lt;a href="https://www.cs.utexas.edu/users/downing/papers/DIP-1996.pdf"&gt;The Dependency Inversion Principle&lt;/a&gt;”, datado de 1996.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementando em Go
&lt;/h2&gt;

&lt;p&gt;Em Go, temos duas construções importantes, sendo structs (para definir objetos, ou seja, agrupamentos de dados correlacionados) e interfaces (para definir comportamentos dos objetos). Interfaces em Go usam um mecanismo de resolução implícito, ou seja, não é necessário declarar explicitamente que algum tipo implementa uma interface específica, isso é feito de maneira implícita, algo análogo a &lt;em&gt;duck typing&lt;/em&gt; em outras linguagens. Interfaces implícitas em Go fazem com que injeção de dependência seja implementada de maneira simples.&lt;/p&gt;

&lt;p&gt;Algumas técnicas simples de implementar de injeção de dependência em Go são:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Injeção por &lt;em&gt;constructor&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Injeção por propriedades (&lt;em&gt;setter methods&lt;/em&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Injeção por constructor
&lt;/h3&gt;

&lt;p&gt;Em injeção por constructor, normalmente, temos uma função &lt;em&gt;factory&lt;/em&gt; (construtora) que vai retornar uma instância de uma struct com suas dependências já associadas.&lt;/p&gt;

&lt;p&gt;Abaixo um exemplo de código em Go.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Repository&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// repo methods&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;AppLogger&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;UseCase&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;     &lt;span class="n"&gt;AppLogger&lt;/span&gt;
    &lt;span class="n"&gt;repository&lt;/span&gt; &lt;span class="n"&gt;Repository&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewUseCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="n"&gt;AppLogger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt; &lt;span class="n"&gt;Repository&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;UserCase&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;UseCase&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;UseCase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// do some stuff&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// injetando as dependências&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// instancias concretas&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;MyLogger&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;repo&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;MyRepository&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="n"&gt;useCase&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;NewUseCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;useCase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Call&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c"&gt;// ... do something&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Um ponto importante a se destacas é que com injeção por &lt;em&gt;constructor&lt;/em&gt; estamos fixando as dependências em tempo de compilação, ou seja, não podemos trocas elas dinamicamente conforme alguma condição. Existem vantagens e desvantagens nesse método.&lt;/p&gt;

&lt;h3&gt;
  
  
  Injeção por propriedades
&lt;/h3&gt;

&lt;p&gt;Na injeção por propriedades utilizamos um método setter para alocar a dependência correta, assim sendo mais flexível, podendo trocar a dependência em tempo de runtime.&lt;/p&gt;

&lt;p&gt;Segue o exemplo para ilustrar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;UseCase&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;     &lt;span class="n"&gt;AppLogger&lt;/span&gt;
    &lt;span class="n"&gt;repository&lt;/span&gt; &lt;span class="n"&gt;Repository&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;UseCase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;SetLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="n"&gt;AppLogger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;UseCase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;SetRepository&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt; &lt;span class="n"&gt;Repository&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;repository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; 

&lt;span class="c"&gt;// injetando as dependências&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// instancias concretas&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;MyLogger&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;repo&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;MyRepo&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="n"&gt;useCase&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;UseCase&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;useCase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;useCase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetRepository&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;useCase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Call&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c"&gt;// ... do something&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Injeção manual vs via framework
&lt;/h2&gt;

&lt;p&gt;No exemplos apresentados estamos utilizando a técnica de injeção manual, ou seja, estamos utilizando ferramentas nativas da linguagem, sem utilizar nenhuma dependência externa, como libs ou frameworks.&lt;/p&gt;

&lt;p&gt;Existem alguns frameworks muito utilizados em Go, como o &lt;a href="https://github.com/google/wire"&gt;Wire&lt;/a&gt; do Google ou o &lt;a href="https://github.com/uber-go/dig"&gt;Dig&lt;/a&gt; da Uber.&lt;/p&gt;

&lt;p&gt;Cada framework possui suas vantagens e desvantagens, além disso, cabe ainda ponderar se faz sentido o uso de um framework ou não.&lt;/p&gt;

&lt;h3&gt;
  
  
  Injeção Manual:
&lt;/h3&gt;

&lt;p&gt;Prós:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Controle completo de como as dependências são injetadas&lt;/li&gt;
&lt;li&gt;Nenhuma dependência externa&lt;/li&gt;
&lt;li&gt;Muito mais simples de entender e não requer nenhum conhecimento extra, como DSL própria&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Contras:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Requer escrita de mais código &lt;em&gt;boilerplate&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Pode ser muito passivo de erro humano, principalmente quando as dependências começam a ficar mais complexas, conforme o projeto cresce&lt;/li&gt;
&lt;li&gt;Acaba sendo muito complexo de manter grafos complexos de dependências&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Framework
&lt;/h3&gt;

&lt;p&gt;Prós:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reduz boilerplate provendo um mecanismo automático&lt;/li&gt;
&lt;li&gt;Ajuda a promover melhores práticas e padrões de design para orquestrar dependências&lt;/li&gt;
&lt;li&gt;Pode ajudar a simplificar testes, ao facilitar o uso de mock objects&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Contras:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adiciona uma dependência externa ao projeto&lt;/li&gt;
&lt;li&gt;Requer o aprendizado de um framework novo, que envolve conceitos, configurações e um DSL&lt;/li&gt;
&lt;li&gt;Pode não ser apropriado para todos os projetos, as vezes pode adicionar problemas de uso de memória ou performance que são cruciais para alguns projetos.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Em linhas gerais, injeção manual ajuda a termos mais controle sobre o processo, porém requer mais código e pode ser mais suscetível a erros, enquanto que um framework automatiza muito desse processo e promove melhores práticas, ao custo de aumentar curva de aprendizado, adicionar uma dependência externa e talvez sacrificar um pouco de performance (como alguns frameworks utilizam de Reflection).&lt;/p&gt;

&lt;p&gt;A escolha vai depender de diversos fatores, como a complexidade do projeto, a familiaridade do time com conceitos de injeção de dependência e os trade-offs entre controle vs conveniência.&lt;/p&gt;

&lt;h2&gt;
  
  
  tl;dr
&lt;/h2&gt;

&lt;p&gt;Go é uma linguagem poderosa que nos permite implementar injeção de dependência de maneira simples, sem a necessidade de frameworks, como frequentemente exigido em linguagens como Java. No entanto, também temos à disposição frameworks em Go, como o Wire da Google, que automatizam parte desse processo.&lt;br&gt;
A decisão de utilizar um framework ou não deve ser cuidadosamente considerada, avaliando as necessidades específicas de cada projeto e os trade-offs entre simplicidade e controle.&lt;/p&gt;

</description>
      <category>go</category>
      <category>designpatterns</category>
      <category>learning</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Meus aprendizados com a Rinha de Backend 2023 Q3</title>
      <dc:creator>Erick Takeshi</dc:creator>
      <pubDate>Thu, 22 Feb 2024 18:03:42 +0000</pubDate>
      <link>https://forem.com/erick_tmr/meus-aprendizados-com-a-rinha-de-backend-2023-q3-49dm</link>
      <guid>https://forem.com/erick_tmr/meus-aprendizados-com-a-rinha-de-backend-2023-q3-49dm</guid>
      <description>&lt;p&gt;A rinha de backend foi um evento incrível, e mesmo não acompanhando em &lt;em&gt;real time&lt;/em&gt;, pude revisar os vídeos e tweets recentemente e fiquei muito impressionado com o que vi. Decidi compilar aqui os principais aprendizados que retirei desta competição.&lt;/p&gt;

&lt;h2&gt;
  
  
  Docker
&lt;/h2&gt;

&lt;p&gt;Docker sempre foi uma ferramenta que eu meio que negligenciei pra falar a verdade :)&lt;/p&gt;

&lt;p&gt;Embora na maioria das empresas não tenhamos muito contato direto com a infraestrutura como desenvolvedores, acredito que valha a pena entender pelo menos o básico&lt;/p&gt;

&lt;p&gt;Indo direto ao ponto, a técnica que realmente fez diferença pra rinha:&lt;/p&gt;

&lt;h3&gt;
  
  
  network_mode: host
&lt;/h3&gt;

&lt;p&gt;Basicamente, com Docker, podemos escolher alguns modos diferente para trabalharmos com rede. O modo padrão é o &lt;code&gt;bridge&lt;/code&gt;, onde o Docker utiliza uma rede interna e privada (ao host) e os containers podem se comunicar uns com os outros através dos seus container names (que são utilizados como hostname), além de poderem se comunicar com a rede externa através de NAT.&lt;/p&gt;

&lt;p&gt;Outro modo é o &lt;code&gt;host&lt;/code&gt;,  onde os containers terão acesso direto as network interfaces do seu host, &lt;strong&gt;o que aumenta a performance&lt;/strong&gt;. No entanto, é necessário ter cuidado para evitar conflitos, já que as portas reais são usadas.&lt;/p&gt;

&lt;p&gt;Como a rinha em si não dizia nada a respeito de restrições de network no &lt;code&gt;docker-compose&lt;/code&gt;, poderíamos aproveitar do modo &lt;code&gt;host&lt;/code&gt; e ganhar uma performance maior.&lt;/p&gt;

&lt;h4&gt;
  
  
  Porquê o network_mode host é mais rápido?
&lt;/h4&gt;

&lt;p&gt;O modo &lt;code&gt;bridge&lt;/code&gt; na verdade que é mais lento, isso porque ele introduz uma camada extra sobre a rede do próprio host, um DNS interno, no qual o Docker precisa traduzir um container name para um endereço IP da rede.&lt;/p&gt;

&lt;p&gt;Lembrando que os ganhos desse modo só são mais perceptíveis quando estamos falando de &lt;strong&gt;containers rodando no mesmo host&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Post de &lt;a href="https://yangwwei.github.io/2017/11/22/docker-network-tests.html"&gt;ref&lt;/a&gt; com alguns benchmarks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Banco de dados
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Monitoramento e benchmarks
&lt;/h3&gt;

&lt;p&gt;Aqui temos uns aprendizados bem interessantes.&lt;/p&gt;

&lt;p&gt;Bancos de dados, especialmente os relacionais, costumam ser ferramentas familiares para os desenvolvedores, porém, são poucos os que possuem conhecimento em benchmarks, otimização e performance.&lt;/p&gt;

&lt;p&gt;Na rinha, ficou claro que o banco de dados em si não era o gargalo, mas sim a configuração correta do mesmo. Para atingir o máximo desempenho nos testes da rinha, era necessário alocar os recursos adequadamente entre a aplicações, load balancer e o banco de dados.&lt;/p&gt;

&lt;p&gt;Em um ambiente aberto e com restrições mais alto nível (algo como a EC2 “crua” da AWS), podemos rodar serviços na mesma máquina, dividindo recursos entre os diferentes serviços, assim como era o cenário da rinha.&lt;/p&gt;

&lt;p&gt;Para mensurar e distribuir corretamente esses recursos, é necessário usar ferramentas de monitoramento nos serviços. Para o banco de dados, temos ferramentas como pgAdmin ou o MySQL Workbench, ajudando a enxergar e tunar o uso de CPU, memória e número de conexões.&lt;/p&gt;

&lt;p&gt;Em resumo, o aprendizado que fica é: não assuma nada, sempre meça e tire suas conclusões. Em alguns casos, estamos restritos por outros fatores, o que facilita o calculo dos recursos necessários  ou número de conexões, mas em casos mais abertos, medir e chegar no número ideal é a melhor abordagem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Queries em campos de texto
&lt;/h3&gt;

&lt;p&gt;Um aprendizado mais específico, nem sempre precisamos lidar com buscas desse tipo, mas é interessante termos essa técnica nosso “cinto de utilidades”.&lt;/p&gt;

&lt;p&gt;Queries com &lt;code&gt;ILIKE&lt;/code&gt; tendem a ser lentas e não conseguimos usar índices de forma convencional. O Postgres tem acesso nativo a index do tipo GIST que servem pra indexar campos de texto e otimizar queries deste tipo. No entanto, mesmo com esse índice, ainda teríamos que executar o &lt;code&gt;OR&lt;/code&gt; em 3 campos diferentes e juntar os resultados, o que ainda tornaria a query lenta.&lt;/p&gt;

&lt;p&gt;A solução inteligente neste caso é unir os 3 campos em um só e indexar via GIST este campo, assim conseguimos atingir máxima performance para este tipo de query especifica.&lt;/p&gt;

&lt;p&gt;Vale ressaltar que utilizar o &lt;code&gt;EXPLAIN&lt;/code&gt; é uma ferramenta valiosa pra nos ajudar a identificar o planejamento da query pelo banco e, assim, traçar estratégias, como essa mencionada, para otimizar queries.&lt;/p&gt;

&lt;p&gt;Um último adendo é que embora o Postgres, ou outros bancos com suporte a &lt;em&gt;full text search&lt;/em&gt;, consigam lidar bem com muitos casos, é importante estar atento se no nosso caso e escala específica já não estamos em um cenário em que ferramentas extremamente especializadas começam a fazer sentido, como o Elasticsearch. Precisamos sempre balancear os tradeoffs e ver quando estamos atingindo o limite da tecnologia que temos no momento e, ai sim, migrar pra outra que atenda nossos requisitos.&lt;/p&gt;

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

&lt;p&gt;Foram muitos aprendizados bacanas com a rinha e eu acho que esse é o tipo de competição saudável que ajuda demais a comunidade, deixo aqui meus agradecimentos ao &lt;a href="https://twitter.com/zanfranceschi"&gt;Zan Franceschi&lt;/a&gt;, organizador e idealizador da rinha e ao &lt;a href="https://twitter.com/AkitaOnRails"&gt;Akita&lt;/a&gt; e o &lt;a href="https://twitter.com/MrPowerGamerBR"&gt;MrPowerGamerBR&lt;/a&gt;, pelos ótimos videos explicativos sobre a rinha.&lt;/p&gt;

&lt;p&gt;Estou ansioso pra acompanhar o que vai rolar nessa rinha de 2024, além de estar planejando e executando minha solução aqui ;)&lt;/p&gt;

</description>
      <category>backend</category>
      <category>database</category>
      <category>docker</category>
      <category>rinha</category>
    </item>
    <item>
      <title>makefile para projetos em Go</title>
      <dc:creator>Erick Takeshi</dc:creator>
      <pubDate>Mon, 19 Feb 2024 20:51:04 +0000</pubDate>
      <link>https://forem.com/erick_tmr/makefile-para-projetos-em-go-2pj7</link>
      <guid>https://forem.com/erick_tmr/makefile-para-projetos-em-go-2pj7</guid>
      <description>&lt;p&gt;&lt;code&gt;make&lt;/code&gt; é uma ferramenta de sistemas Unix utilizada para &lt;em&gt;buildar&lt;/em&gt; programas desde 1976.&lt;/p&gt;

&lt;p&gt;Embora seja muito utilizada em, e frequentemente associada a, desenvolvimento de programas em C ou C++, o &lt;code&gt;make&lt;/code&gt; também foi a ferramenta adotada pela comunidade Go para automatizar tarefas relacionadas a projetos.&lt;/p&gt;

&lt;p&gt;Para utilizar o &lt;code&gt;make&lt;/code&gt; precisamos de um arquivo, que por convenção é chamado de Makefile, que contem as instruções de como &lt;em&gt;buildar&lt;/em&gt; um determinado projeto.&lt;/p&gt;

&lt;p&gt;Abaixo segue exemplo de um Makefile qualquer para um programa em Go:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;
&lt;span class="c"&gt;# variaveis
&lt;/span&gt;&lt;span class="nv"&gt;GOCMD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; go
&lt;span class="nv"&gt;GOLINT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; golint

&lt;span class="c"&gt;# declaracao da instrucao default
&lt;/span&gt;&lt;span class="nv"&gt;.DEFAULT_GOAL&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; build

&lt;span class="c"&gt;# rules
&lt;/span&gt;&lt;span class="nl"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;$(&lt;/span&gt;GOCMD&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;fmt&lt;/span&gt; ./...
&lt;span class="nl"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;fmt&lt;/span&gt;

&lt;span class="nl"&gt;lint&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;fmt&lt;/span&gt;
    &lt;span class="p"&gt;$(&lt;/span&gt;GOLINT&lt;span class="p"&gt;)&lt;/span&gt; ./...
&lt;span class="nl"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;lint&lt;/span&gt;

&lt;span class="nl"&gt;vet&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;fmt&lt;/span&gt;
    &lt;span class="p"&gt;$(&lt;/span&gt;GOCMD&lt;span class="p"&gt;)&lt;/span&gt; vet ./...
&lt;span class="nl"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;vet&lt;/span&gt;

&lt;span class="nl"&gt;build&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;vet&lt;/span&gt;
    &lt;span class="p"&gt;$(&lt;/span&gt;GOCMD&lt;span class="p"&gt;)&lt;/span&gt; build
&lt;span class="nl"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Passando por cada aspecto rapidamente:&lt;br&gt;
Nas primeiras linhas do arquivo vemos a declaração de algumas variáveis que serão uteis para nos ajudar a chamar o comando nas regras definidas (rules). A variável GOCMD é especialmente interessante pois podemos deixar nosso Makefile agnóstico de versões do Go, uma vez que, com o suporte built-in da linguagem, deverias ter algo como go1.15.2 quando quisermos utilizar uma versão específica.&lt;/p&gt;

&lt;p&gt;Na linha seguinte temos a declaração do .DEFAULT_GOAL que atribui qual vai ser a regra a ser rodada quando não passarmos nenhum argumento pro executável do &lt;code&gt;make&lt;/code&gt;, ou seja, invocando somente &lt;code&gt;make&lt;/code&gt; no diretório do projeto vai executar a regra “build”.&lt;/p&gt;

&lt;p&gt;Nas próximas linhas temos as declarações de regras (rules), essas regras são os nomes que damos a sequencias de instruções shell que deverão ser executadas, alem disso, temos a declaração do .PHONY, uma instrução que dita que aquela regra em específico não produz um arquivo como output.&lt;/p&gt;

&lt;p&gt;A estrutura de uma regra é a seguinte:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nl"&gt;nome_da_regra&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;dep1 dep2&lt;/span&gt;
    intructions
    instruction2

&lt;span class="nl"&gt;dep1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Um importante aspecto que precisamos nos atentar é que o &lt;code&gt;make&lt;/code&gt; é bem “chato” quando se trata de espaçamento antes das instruções shell, precisamos sempre usar &lt;em&gt;tab&lt;/em&gt; ante de cada instrução, não podemos usar espaços (como usamos por padrão em alguns editores de texto, é bom verificar).&lt;/p&gt;

&lt;h2&gt;
  
  
  OBS sobre o Makefile de exemplo
&lt;/h2&gt;

&lt;p&gt;Em projetos de produção você vai querer utilizar o &lt;a href="https://github.com/golangci/golangci-lint"&gt;golangci-lint&lt;/a&gt;, linter que faz o uso de diversos linter por debaixo dos panos, de maneira otimizada.&lt;/p&gt;

&lt;p&gt;Utilizamos linters e formatters de maneira separada em cada rule para poder exemplificar como ficaria um Makefile um pouco mais complexo.&lt;/p&gt;

&lt;h2&gt;
  
  
  tl;dr
&lt;/h2&gt;

&lt;p&gt;Em resumo, o uso do Makefile para projetos em Go representa uma abordagem eficaz e padronizada para automatizar tarefas relacionadas ao desenvolvimento, proporcionando uma estrutura organizada para execução de comandos essenciais. A flexibilidade oferecida pelo Makefile permite que desenvolvedores configurem regras específicas, como formatação, linting e compilação, de forma concisa e fácil de manter.&lt;/p&gt;

</description>
      <category>go</category>
      <category>devops</category>
      <category>beginners</category>
      <category>learning</category>
    </item>
  </channel>
</rss>
