<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Sistemas-Distribuidos | AkitaOnRails.com</title><link>https://akitaonrails.github.io/tags/sistemas-distribuidos/</link><description>Blog do Fabio Akita do Canal do YouTube 'Akitando' falando sobre tecnologia, carreira e coisas geek.</description><generator>Hugo -- gohugo.io</generator><language>pt-BR</language><lastBuildDate>Mon, 19 Oct 2026 23:20:49 +0000</lastBuildDate><atom:link href="https://akitaonrails.github.io/tags/sistemas-distribuidos/index.xml" rel="self" type="application/rss+xml"/><item><title>Jobs Assíncronos que sobrevivem ao Caos | Bastidores do The M.Akita Chronicles</title><link>https://akitaonrails.github.io/2026/02/19/jobs-asssincronos-que-sobrevivem-ao-caos-bastidores-do-the-m-akita-chronicles/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/02/19/jobs-asssincronos-que-sobrevivem-ao-caos-bastidores-do-the-m-akita-chronicles/</guid><pubDate>Thu, 19 Feb 2026 01:10:08 +0000</pubDate><description>&lt;p&gt;Este post vai fazer parte de uma série; acompanhe pela tag &lt;a href="https://akitaonrails.github.io/tags/the-m-akita-chronicles/"&gt;/themakitachronicles&lt;/a&gt;. Esta é a parte 5.&lt;/p&gt;
&lt;p&gt;E não deixe de assinar minha nova newsletter &lt;a href="https://themakitachronicles.com/"target="_blank" rel="noopener"&gt;The M.Akita Chronicles&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;&amp;ndash;&lt;/p&gt;
&lt;p&gt;Vou ser direto: a maioria dos desenvolvedores trata background jobs como se fossem scripts que rodam uma vez e pronto. &amp;ldquo;Ah, coloca num Sidekiq e tá bom.&amp;rdquo; Não, &lt;strong&gt;não tá bom&lt;/strong&gt;. Jobs que processam coisas importantes — enviar emails, publicar conteúdo, chamar APIs externas — precisam ser tratados como cidadãos de primeira classe na sua arquitetura.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Este post vai fazer parte de uma série; acompanhe pela tag <a href="/tags/the-m-akita-chronicles/">/themakitachronicles</a>. Esta é a parte 5.</p>
<p>E não deixe de assinar minha nova newsletter <a href="https://themakitachronicles.com/"target="_blank" rel="noopener">The M.Akita Chronicles</a>!</p>
<p>&ndash;</p>
<p>Vou ser direto: a maioria dos desenvolvedores trata background jobs como se fossem scripts que rodam uma vez e pronto. &ldquo;Ah, coloca num Sidekiq e tá bom.&rdquo; Não, <strong>não tá bom</strong>. Jobs que processam coisas importantes — enviar emails, publicar conteúdo, chamar APIs externas — precisam ser tratados como cidadãos de primeira classe na sua arquitetura.</p>
<p>Neste post vou mostrar como o Rails 8 com ActiveJob e SolidQueue mudou minha perspectiva sobre processamento assíncrono. Não é teoria — são padrões que emergiram de um sistema real rodando em produção toda semana.</p>
<h2>O Problema: Jobs São Frágeis por Natureza<span class="hx:absolute hx:-mt-20" id="o-problema-jobs-são-frágeis-por-natureza"></span>
    <a href="#o-problema-jobs-s%c3%a3o-fr%c3%a1geis-por-natureza" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Pense no cenário: você tem um job que monta uma newsletter, publica num blog via API do GitHub, espera um podcast ficar pronto, e depois dispara emails para centenas de assinantes. Qualquer passo pode falhar. A API pode dar timeout. O podcast pode demorar mais que o esperado. O servidor pode reiniciar no meio do envio.</p>
<p>Se você tratar isso como um script linear, vai sofrer. A pergunta certa não é &ldquo;como faço esse job rodar?&rdquo; — é <strong>&ldquo;como faço esse job se recuperar quando algo der errado?&rdquo;</strong></p>
<h2>Padrão 1: retry_on Com Exceções Específicas<span class="hx:absolute hx:-mt-20" id="padrão-1-retry_on-com-exceções-específicas"></span>
    <a href="#padr%c3%a3o-1-retry_on-com-exce%c3%a7%c3%b5es-espec%c3%adficas" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O <code>retry_on</code> do ActiveJob é absurdamente poderoso, mas a maioria usa errado. Olha o anti-padrão clássico:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">MeuJob</span> <span class="o">&lt;</span> <span class="no">ApplicationJob</span>
</span></span><span class="line"><span class="cl">  <span class="n">retry_on</span> <span class="no">StandardError</span><span class="p">,</span> <span class="ss">wait</span><span class="p">:</span> <span class="mi">5</span><span class="o">.</span><span class="n">seconds</span><span class="p">,</span> <span class="ss">attempts</span><span class="p">:</span> <span class="mi">3</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Isso re-tenta <strong>qualquer</strong> erro 3 vezes e desiste. Serve pra quase nada no mundo real. O padrão que funciona é criar exceções específicas para estados transitórios:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">PodcastNotReady</span> <span class="o">&lt;</span> <span class="no">StandardError</span><span class="p">;</span> <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">PublishAndSendJob</span> <span class="o">&lt;</span> <span class="no">ApplicationJob</span>
</span></span><span class="line"><span class="cl">  <span class="n">retry_on</span> <span class="no">PodcastNotReady</span><span class="p">,</span> <span class="ss">wait</span><span class="p">:</span> <span class="mi">15</span><span class="o">.</span><span class="n">minutes</span><span class="p">,</span> <span class="ss">attempts</span><span class="p">:</span> <span class="mi">16</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">perform</span><span class="p">(</span><span class="n">newsletter_id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">metadata</span> <span class="o">=</span> <span class="n">check_podcast_metadata</span><span class="p">(</span><span class="n">newsletter_id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">raise</span> <span class="no">PodcastNotReady</span><span class="p">,</span> <span class="s2">&#34;Aguardando podcast&#34;</span> <span class="k">unless</span> <span class="n">metadata</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># continua o trabalho...</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Percebe o que está acontecendo? O job espera até <strong>4 horas</strong> (15min × 16) pelo podcast ficar pronto, fazendo polling a cada 15 minutos. Não é um loop infinito — tem um limite claro. E quando estoura esse limite, você trata o timeout explicitamente:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">rescue</span> <span class="no">PodcastNotReady</span> <span class="o">=&gt;</span> <span class="n">e</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="n">executions</span> <span class="o">&gt;=</span> <span class="mi">16</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Publica sem podcast e avisa a equipe</span>
</span></span><span class="line"><span class="cl">    <span class="n">publish_without_podcast</span><span class="p">(</span><span class="n">newsletter</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">notify_team</span><span class="p">(</span><span class="s2">&#34;Podcast não ficou pronto a tempo&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">else</span>
</span></span><span class="line"><span class="cl">    <span class="k">raise</span> <span class="c1"># re-raise pra SolidQueue agendar o próximo retry</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Isso é <strong>degradação graciosa</strong>. O sistema não para porque um componente falhou — ele se adapta e continua.</p>
<h2>Padrão 2: Locks Distribuídos<span class="hx:absolute hx:-mt-20" id="padrão-2-locks-distribuídos"></span>
    <a href="#padr%c3%a3o-2-locks-distribu%c3%addos" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Quando você tem múltiplos caminhos que podem disparar o mesmo job (um cron de segurança, um trigger manual, uma API), precisa garantir que só uma instância roda por vez.</p>
<p>O conceito é simples: um lock baseado em arquivo (ou banco) que expira automaticamente:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">DeployLock</span>
</span></span><span class="line"><span class="cl">  <span class="no">LOCK_DIR</span> <span class="o">=</span> <span class="no">Rails</span><span class="o">.</span><span class="n">root</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="s2">&#34;tmp/locks&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="no">DEFAULT_TTL</span> <span class="o">=</span> <span class="mi">30</span><span class="o">.</span><span class="n">minutes</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">with_lock</span><span class="p">(</span><span class="nb">name</span><span class="p">,</span> <span class="ss">ttl</span><span class="p">:</span> <span class="no">DEFAULT_TTL</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">acquire!</span><span class="p">(</span><span class="nb">name</span><span class="p">,</span> <span class="ss">ttl</span><span class="p">:</span> <span class="n">ttl</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">yield</span>
</span></span><span class="line"><span class="cl">  <span class="k">ensure</span>
</span></span><span class="line"><span class="cl">    <span class="n">release</span><span class="p">(</span><span class="nb">name</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Com SQLite, isso é trivial — sem Redis, sem coordenação externa. O lock tem TTL pra não travar se o processo morrer. E o <code>ensure</code> garante que libera mesmo se o bloco levantar exceção.</p>
<h2>Padrão 3: Envio Atômico de Emails<span class="hx:absolute hx:-mt-20" id="padrão-3-envio-atômico-de-emails"></span>
    <a href="#padr%c3%a3o-3-envio-at%c3%b4mico-de-emails" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Aqui é onde a maioria dos sistemas de newsletter falha espetacularmente. O cenário: você está enviando 500 emails e o servidor reinicia no email 247. O que acontece quando o job re-executa?</p>
<p>Se você só iterou uma lista, vai reenviar os primeiros 247. Seus assinantes vão adorar receber a newsletter duas vezes.</p>
<p>A solução é <strong>claiming atômico</strong> por destinatário:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># Antes de enviar, cria um registro por assinante</span>
</span></span><span class="line"><span class="cl"><span class="n">subscribers</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="nb">sub</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="no">EmailDelivery</span><span class="o">.</span><span class="n">create!</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="ss">newsletter</span><span class="p">:</span> <span class="n">newsletter</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="ss">subscriber</span><span class="p">:</span> <span class="nb">sub</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="ss">status</span><span class="p">:</span> <span class="s2">&#34;pending&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Na hora de enviar, faz claim atômico</span>
</span></span><span class="line"><span class="cl"><span class="n">delivery</span> <span class="o">=</span> <span class="no">EmailDelivery</span>
</span></span><span class="line"><span class="cl">  <span class="o">.</span><span class="n">where</span><span class="p">(</span><span class="ss">status</span><span class="p">:</span> <span class="o">[</span><span class="s2">&#34;pending&#34;</span><span class="p">,</span> <span class="s2">&#34;failed&#34;</span><span class="o">]</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="o">.</span><span class="n">lock</span>
</span></span><span class="line"><span class="cl">  <span class="o">.</span><span class="n">first</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">delivery</span><span class="o">.</span><span class="n">update!</span><span class="p">(</span><span class="ss">status</span><span class="p">:</span> <span class="s2">&#34;sending&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">send_email</span><span class="p">(</span><span class="n">delivery</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">delivery</span><span class="o">.</span><span class="n">update!</span><span class="p">(</span><span class="ss">status</span><span class="p">:</span> <span class="s2">&#34;sent&#34;</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Se o servidor morrer entre <code>sending</code> e <code>sent</code>, o registro fica como &ldquo;sending&rdquo; — e um job de recuperação (<code>RecoverStaleDeliveriesJob</code>) periodicamente move esses registros para &ldquo;unknown&rdquo; depois de um timeout, pra que nunca sejam automaticamente reenviados. Emails ambíguos <strong>nunca</strong> são reenviados automaticamente. Esse é o tipo de detalhe que separa um sistema hobby de um sistema de produção.</p>
<h2>Padrão 4: Jobs Orquestradores<span class="hx:absolute hx:-mt-20" id="padrão-4-jobs-orquestradores"></span>
    <a href="#padr%c3%a3o-4-jobs-orquestradores" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Um erro comum é colocar lógica demais num único job. O padrão que funciona é ter <strong>jobs orquestradores</strong> que delegam para jobs especializados:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>PublishAndSendJob (orquestrador)
  ├── Espera podcast (retry_on PodcastNotReady)
  ├── PublishToBlogJob.publish(newsletter)
  └── SendNewsletterJob.perform_now(newsletter.id)
        └── SendSingleEmailJob (por assinante)</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O orquestrador coordena a sequência. Cada job especializado sabe fazer exatamente uma coisa e pode ser re-executado independentemente. Se o envio de emails falhar, você pode re-disparar só o <code>SendNewsletterJob</code> sem republicar o blog.</p>
<h2>Padrão 5: Safety Nets com Cron<span class="hx:absolute hx:-mt-20" id="padrão-5-safety-nets-com-cron"></span>
    <a href="#padr%c3%a3o-5-safety-nets-com-cron" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Não confie em uma única cadeia de execução. O cron do SolidQueue (<code>config/recurring.yml</code>) serve como rede de segurança:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">send_newsletter</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">class</span><span class="p">:</span><span class="w"> </span><span class="l">SendNewsletterJob</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">schedule</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;0 12 * * 1&#34;</span><span class="w">  </span><span class="c"># Segunda 9h BRT (UTC-3)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Se o <code>PublishAndSendJob</code> das 7h falhou catastroficamente, o cron das 9h dispara o <code>SendNewsletterJob</code> como fallback. O job verifica se a newsletter já foi enviada e faz no-op se sim. <strong>Idempotência</strong> é a palavra-chave aqui — o job pode rodar quantas vezes quiser e o resultado é sempre o mesmo.</p>
<p>Mas cuidado com o inverso: um job que se reagenda infinitamente quando não encontra trabalho é uma bomba-relógio. Se o job não encontra newsletter pronta, ele simplesmente retorna. O cron cuida de tentar de novo na próxima semana.</p>
<h2>Padrão 6: Notificações de Status<span class="hx:absolute hx:-mt-20" id="padrão-6-notificações-de-status"></span>
    <a href="#padr%c3%a3o-6-notifica%c3%a7%c3%b5es-de-status" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Todo job de longa duração deve comunicar seu progresso. Um concern simples resolve:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">module</span> <span class="nn">DiscordStatus</span>
</span></span><span class="line"><span class="cl">  <span class="kp">extend</span> <span class="no">ActiveSupport</span><span class="o">::</span><span class="no">Concern</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">notify_start</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="no">DiscordNotifier</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="ss">channel</span><span class="p">:</span> <span class="n">status_channel</span><span class="p">,</span> <span class="ss">text</span><span class="p">:</span> <span class="s2">&#34;▶️ </span><span class="si">#{</span><span class="n">message</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">notify_done</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="no">DiscordNotifier</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="ss">channel</span><span class="p">:</span> <span class="n">status_channel</span><span class="p">,</span> <span class="ss">text</span><span class="p">:</span> <span class="s2">&#34;✅ </span><span class="si">#{</span><span class="n">message</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">notify_error</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="no">DiscordNotifier</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="ss">channel</span><span class="p">:</span> <span class="n">status_channel</span><span class="p">,</span> <span class="ss">text</span><span class="p">:</span> <span class="s2">&#34;❌ </span><span class="si">#{</span><span class="n">message</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Cada job inclui o concern e chama <code>notify_start</code> no início, <code>notify_done</code> no sucesso, <code>notify_error</code> no rescue. Quando algo dá errado às 3 da manhã, você acorda e vê exatamente onde parou — sem precisar cavar logs.</p>
<h2>SolidQueue: O Fim da Dependência de Redis<span class="hx:absolute hx:-mt-20" id="solidqueue-o-fim-da-dependência-de-redis"></span>
    <a href="#solidqueue-o-fim-da-depend%c3%aancia-de-redis" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Uma nota sobre SolidQueue, que veio como padrão no Rails 8: usar o mesmo banco SQLite para jobs e dados da aplicação simplifica <strong>drasticamente</strong> a operação. Não precisa de Redis rodando separado. Não precisa se preocupar com o Redis reiniciar e perder jobs que estavam na memória.</p>
<p>Os jobs ficam persistidos no banco. Se o servidor reiniciar, eles estão lá esperando. O retry state é preservado. É absurdamente mais simples que a alternativa, e pra 99% dos casos, a performance é mais que suficiente.</p>
<h2>Conclusão<span class="hx:absolute hx:-mt-20" id="conclusão"></span>
    <a href="#conclus%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O Rails 8 com ActiveJob e SolidQueue não inventou nada revolucionário. O que ele fez foi deixar ridiculamente fácil de implementar padrões que antes precisavam de infraestrutura pesada:</p>
<ul>
<li><strong>retry_on</strong> com exceções específicas e limites claros</li>
<li><strong>Locks distribuídos</strong> com TTL automático</li>
<li><strong>Claiming atômico</strong> pra operações que não podem duplicar</li>
<li><strong>Jobs orquestradores</strong> que delegam e se recuperam</li>
<li><strong>Crons de segurança</strong> como fallback</li>
<li><strong>Notificações</strong> pra visibilidade operacional</li>
</ul>
<p>Nenhum desses padrões é complicado sozinho. Mas juntos, é o que faz a diferença entre um sistema que quebra na primeira falha e um que aguenta o tranco em produção — e te deixa dormir tranquilo na segunda-feira.</p>
]]></content:encoded><category>the-m-akita-chronicles</category><category>rails</category><category>sistemas-distribuidos</category></item><item><title>[Akitando] #143 - Discutindo sobre Banco de Dados - Dos primórdios a Big Data</title><link>https://akitaonrails.github.io/2023/07/22/akitando-143-discutindo-sobre-banco-de-dados-dos-primordios-a-big-data/</link><guid isPermaLink="true">https://akitaonrails.github.io/2023/07/22/akitando-143-discutindo-sobre-banco-de-dados-dos-primordios-a-big-data/</guid><pubDate>Sat, 22 Jul 2023 08:00:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/Bfm3Ms2cTg0"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;Não se pode ser um bom programador sem entender de verdade como bancos de dados funcionam. E não se pode entender banco de dados sem entender algoritmos e estruturas de dados. E hoje vou mostrar como dos primeiros bancos de dados dos anos 80 chegamos aos SQL e NoSQL atuais.&lt;/p&gt;
&lt;p&gt;Este video complementa meu video anterior &amp;ldquo;Fiz um Servidor de SQL?&amp;rdquo; e outros videos que vou referenciando ao longo deste. Anote tudo e assistam depois.&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/Bfm3Ms2cTg0"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>Não se pode ser um bom programador sem entender de verdade como bancos de dados funcionam. E não se pode entender banco de dados sem entender algoritmos e estruturas de dados. E hoje vou mostrar como dos primeiros bancos de dados dos anos 80 chegamos aos SQL e NoSQL atuais.</p>
<p>Este video complementa meu video anterior &ldquo;Fiz um Servidor de SQL?&rdquo; e outros videos que vou referenciando ao longo deste. Anote tudo e assistam depois.</p>
<h2>Capítulos<span class="hx:absolute hx:-mt-20" id="capítulos"></span>
    <a href="#cap%c3%adtulos" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li>00:00:00 - Intro</li>
<li>00:01:15 - CAP 01 - O que é um Banco de Dados? A era do dBase</li>
<li>00:10:04 - CAP 02 - Procurando com Índices - NDX e B-Tree</li>
<li>00:18:53 - CAP 03 - Do DBF ao SQLite3 - O banco mais usado do Mundo</li>
<li>00:23:42 - CAP 04 - Tudo são Árvores B - A estrutura mais importante da computaçãot</li>
<li>00:28:51 - CAP 05 - JSON no SQL?? Você não está entendendo</li>
<li>00:31:45 - CAP 06 - Proteção e Consistência - As garantias dos bancos</li>
<li>00:35:38 - CAP 07 - Do xBase a Cliente-Servidor - As 3 Camadas</li>
<li>00:38:59 - CAP 08 - Programando Errado - Você não sabe usar o servidor</li>
<li>00:45:26 - CAP 09 - Constraints - Usando do jeito certo</li>
<li>00:48:13 - CAP 10 - Monitorando - Sempre monitore tudo!</li>
<li>00:50:23 - CAP 11 - Big Data - Virando gente grande</li>
<li>00:53:40 - CAP 12 - Dividindo pra Conquistar - Acelerando seu App Web</li>
<li>00:56:28 - CAP 13 - Big Data Parte 2 - Por que NoSQL?</li>
<li>01:00:39 - CAP 14 - Teorema CAP - As partes importantes</li>
<li>01:07:12 - CAP 15 - Estratégias de Chaves Primárias - Chaves Surrogadas</li>
<li>01:11:25 - CAP 16 - Hashing Consistente - Sharding</li>
<li>01:18:42 - CAP 17 - O Problema de Cloud - Calculando Precificação</li>
<li>01:24:51 - CAP 18 - Recomendação pra Iniciantes - Arroz com Feijão</li>
<li>01:27:39 - Bloopers</li>
</ul>
<h2>Links<span class="hx:absolute hx:-mt-20" id="links"></span>
    <a href="#links" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li><a href="https://www.heroku.com/postgres"target="_blank" rel="noopener">Heroku</a></li>
<li><a href="https://aws.amazon.com/elasticache/"target="_blank" rel="noopener">Amazon ElastiCache</a></li>
<li><a href="https://aws.amazon.com/rds/"target="_blank" rel="noopener">Amazon RDS</a></li>
<li><a href="https://aws.amazon.com/pt/rds/aurora/"target="_blank" rel="noopener">Amazon Aurora</a></li>
<li><a href="https://aws.amazon.com/s3/storage-classes/glacier/"target="_blank" rel="noopener">Amazon S3 Glacier Storage Classes | AWS</a></li>
<li><a href="https://www.datastax.com/products/datastax-astra"target="_blank" rel="noopener">Astra DB | DataStax</a></li>
<li><a href="https://www.scylladb.com/product/scylla-cloud/"target="_blank" rel="noopener">Scylla Cloud</a></li>
<li><a href="https://www.sap.com/products/technology-platform/hana.html"target="_blank" rel="noopener">SAP HANA Cloud</a></li>
<li><a href="https://www.oracle.com/big-data/big-data-sql/"target="_blank" rel="noopener">Big Data SQL Cloud Service | Oracle</a></li>
<li><a href="https://azure.microsoft.com/en-us/products/azure-sql/database"target="_blank" rel="noopener">Azure SQL Database</a></li>
<li><a href="https://cloud.google.com/sql"target="_blank" rel="noopener">Google Cloud</a></li>
<li><a href="https://www.mongodb.com/pricing"target="_blank" rel="noopener">Pricing | MongoDB</a></li>
<li><a href="https://valentina-db.com/en/valentina-sqlite-server"target="_blank" rel="noopener">Valentina SQLite Server</a></li>
<li><a href="https://calculator.aws/#/addService/DynamoDB"target="_blank" rel="noopener">Configure Amazon DynamoDB (calculator.aws)</a></li>
<li><a href="https://www.infoq.com/articles/cap-twelve-years-later-how-the-rules-have-changed/"target="_blank" rel="noopener">CAP Twelve Years Later: How the &ldquo;Rules&rdquo; Have Changed (infoq.com)</a></li>
<li>[Dynamo: Amazon’s highly available key-value store - Amazon Science](<a href="https://www.amazon.science/publications/dynamo-amazons-highly-available-key-value-store"target="_blank" rel="noopener">https://www.amazon.science/publications/dynamo-amazons-highly-available-key-value-store</a></li>
<li><a href="https://www.postgresqltutorial.com/postgresql-tutorial/postgresql-json/"target="_blank" rel="noopener">PostgreSQL JSON Tutorial (postgresqltutorial.com)</a></li>
<li><a href="https://github.com/azadkuh/sqlite-amalgamation"target="_blank" rel="noopener">GitHub - azadkuh/sqlite-amalgamation: The SQLite amalgamation mirror with cmake</a></li>
</ul>
<h2>SCRIPT<span class="hx:absolute hx:-mt-20" id="script"></span>
    <a href="#script" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Olá pessoal, Fabio Akita</p>
<p>Desde que comecei o canal, banco de dados é um dos temas que eu mais queria discutir, mas vim postergando pra poder explicar fundamentos antes. O objetivo não é ensinar SQL ou MongoDB nem nenhum outro em detalhes, é dar perspectiva pra quem está iniciando entender porque diabos existem tantos tipos diferentes de bancos de dados e quando se deve considerar usar um em vez do outro. Vai ser um video mais alto nível, sem tanto código em si, mas sim dar insights pra vocês pesquisarem nas respectivas documentações depois. Vou começar nos primórdios e só na metade vamos começar a cair em SQL e NoSQL. Então, vamos lá.</p>
<p>(&hellip;)</p>
<p>Infelizmente, se você estava esperando um video sobre modelagem, modelo entidade-relacional, ORMs, design patterns e coisas assim, ainda não é este video. Objetivo é falar mais o que acontece por baixo dos panos, as tecnologias que formam um banco de dados. Eu acho que modelagem só é efetiva se você sabe como a plataforma por baixo realmente funciona, senão modelagem vira só superstição e você só acha que sua modelagem é boa sem realmente saber o que ela causa por baixo. Tudo que você não sabe como realmente funciona vira superstição.</p>
<p>Sempre falo que uma das vantagens que eu tive, foi conseguir testemunhar as versões 1.0 de quase tudo que surgiu nos últimos 40 anos. Se você pegar um banco de dados moderno, como um DynamoDB da AWS, ou um Postgres, eles tem dezenas de funcionalidades que não são imediatamente óbvios pra que foram criados. Sem saber isso, você acaba tentando usar nos lugares errados ou acha que são ruins, ou acha que você que é burro e não consegue usar.</p>
<p>Pense por um segundo, o que é um banco de dados? À primeira vista poderíamos dizer que é um programa que facilita e gerencia o armazenamento de dados estruturados. Pense um armário com gavetas, cada gaveta com um tipo de formulário. As gavetas seriam nossas tabelas e o armário seria o banco de dados. Acho que é parecido com o que a maioria de vocês deve imaginar.</p>
<p>Pra iniciantes, acho que vale a pena retornar aos fundamentos. Por isso sempre falo tanto que é importante aprender a fundação, como algoritmos e estruturas de dados. O problema é que a estrutura mais simples de aprender e por causa disso comum de usar são listas. Essas seriam as gavetas. Em particular listas ligadas ou arrays puros. E se escolher partir desse ponto, vai ter problemas. Vamos entender.</p>
<p>O banco de dados mais simples de todos, um armário só com uma gaveta, é um mero array em memória com registros, como um struct ou uma instância de uma classe, um objeto. Lembram como um array é estruturado em memória? Um array de inteiros com 10 elementos, provavelmente tem 10 vezes 64 bits ou 8 bytes, ocupando 80 bytes no total. Todo array tem elementos de mesmo tamanho fixo, sempre.</p>
<p>Se eu quiser pegar o 5o elemento desse array, basta contar que tenho 4 elementos antes, vezes 8 bytes, então 32 bytes pra frente, vou achar o 5o elemento. Se eu souber qual posição do array quero encontrar, independe se está no começo ou no fim, o tempo de acesso é o mesmo, tempo constante. O array pode ter 10 elementos ou 10 mil elementos. Leva mais ou menos o mesmo tempo pra acessar qualquer posição.</p>
<p>Quando todos os meus dados cabem na memória RAM, toda operação é simples. Mesmo se eu não souber a posição, na pior das hipóteses faço força bruta via um loop do primeiro até o último elemento, até encontrar. E isso seria de longe, o jeito mais tosco de se procurar alguma coisa numa lista, mas pra conjuntos de dados pequenos, quase ninguém nota a diferença. Mas deixa eu repetir: procurar dados numa lista com um grande loop e if dentro é o jeito mais absurdamente horrendo e inaceitável. Vou repetir de novo pra ficar claro: se você está fazendo um loop pra procurar itens numa lista, você é insano!</p>
<p>Isso porque todo programador, especialmente iniciantes, sempre lidam com poucos dados. É muito difícil entender complexidade de algoritmos e diferença nos tempos de processamentos se só tem cem, mil elementos. Só quando começar a lidar com centenas de milhares, milhões de elementos, que vai começar a entender a diferença. Já vou chegar nisso.</p>
<p>Enfim. Imagine que lá no começo da computação, todo mundo lida com poucos dados comparado com hoje. Por isso as primeiras soluções são simples. Um array com elementos de tamanho fixo é a forma mais fácil de pensar. E quando acaba a memória? Digamos, uma lista de produtos ou lista de clientes num ecommerce? Obviamente não cabe tudo em RAM. Daí temos que pensar em gravar esses dados em disco. E a forma mais simples de fazer isso é o bom e velho arquivo append only, onde vamos adicionando novos elementos no fim do arquivo.</p>
<p>É fácil. Pense num registro como uma struct de C, ou uma classe de Javascript ou Python, só que garantindo que todos os elementos tenham tamanho fixo. Determino uma struct com o primeiro campo sendo o nome completo, com umas 100 letras, onde cada uma é representada por um código UTF-8, de 8 bits,  100 bytes no total. Depois temos um número de telefone, com DDD de 3 caracteres, um prefixo de 3 números como era antigamente e um sufixo de 4 números, mais parênteses e hífen, total de 13 letras ou 13 bytes.</p>
<p>Temos um total de 100 bytes mais 13, ou 113 bytes. O primeiro registro vai ocupar o começo do arquivo começando no endereço zero e indo até o endereço 112 bytes. O segundo registro começa no byte 113 e vai até 225 e assim por diante. Posso abrir o arquivo, ir até o final e gravar mais um registro. E assim fica fácil sair cadastrando novos registros.</p>
<p>De curiosidade, queria aproveitar pra mostrar a segunda linguagem e ambiente de programação que aprendi depois do Basic, quando eu era pré-adolescente. Acho que era 1990 e tinha uns 13 ou 14 anos. Meu pai tinha pedido pro meu tio trazer um PC do Paraguai, era um PC-AT 286 com 1 megabyte de RAM, 10 ou 20 mega de HD. Veio instalado com MS-DOS 3.0 e meu tio tinha deixado também o programa que ele usava pra trabalhar: o dBase III Plus, ainda da antiga empresa Ashton-Tate.</p>
<p>E era só isso que tinha instalado. Não tinha internet naquela época. Eu não tinha livro nem nada. Por acaso um outro primo meu tava estudando isso e me emprestou uma apostila do curso que  tava fazendo. Foi meu primeiro PC, então só de ver ligado e ter um teclado que podia usar, em vez de uma máquina mecânica de datilografia, já achava incrível. Minha primeira ideia foi copiar a apostila do meu primo. Eu redigitei tudo do zero acho que no Wordstar.</p>
<p>Este é o Wordstar. Ele tem um aspecto esquisito porque tá configurado no modo de 50 linhas que o VGA permite, em vez de 25 linhas de CGA. Essa versão é uma das últimas que saiu pra DOS, acho que a 7.0, daí tem ajudas que as versões iniciais não tinham, com um menu completo com todas as opções. Senão a única forma de usar era lembrar dos atalhos como control mais P e B pra iniciar modo negrito, daí repetir control P e B pra voltar pro modo normal. Control P e Y pra itálico e assim por diante.</p>
<p>Era tão moderno que tinha até um Preview em modo gráfico pra mostrar como ficaria na impressora. Essa época estávamos na transição do modo texto de DOS pro modo gráfico de Windows 3. Muitos editores de texto atuais como o próprio VS Code ou Obsidian da vida tem modo de ficar focado que deixa o texto no centro e esconde toda distração. São inspirados em editores antigos como o Wordstar. Esse é o editor que o George R. R. Martin diz que usa pra escrever Game of Thrones.</p>
<p>Enfim, pequena tangente pra mostrar como eu copiei a apostila de dBase. Mais pra frente consegui comprar livros de verdade. Acho que nenhuma linguagem comprei tantos livros pra aprender quanto dBase e seu sucessor, o Clipper. Olha só os que ainda guardei. A razão de tanto livro é que antigamente não tinha Google nem Stackoverflow, lembrem-se disso. Depois que surgiu a Web, já não precisei de tanto livro assim.</p>
<p>Abrindo o dBase, tem um REPL, um interpretador de comandos, que nem quando se digita <code>node</code> ou <code>python</code> e dá pra começar a digitar comandos. Mas também tem um sistema de menus aqui pra facilitar. Olha só, posso escolher a opção de criar um novo banco de dados. Digito o campo &ldquo;nome&rdquo;, tipo &ldquo;character&rdquo; com 100 letras. Depois o campo &ldquo;telefone&rdquo;, tipo &ldquo;character&rdquo; também, com 13 letras. Vou aproveitar e adicionar um campo de &ldquo;empresa&rdquo; com 100 letras. Pronto. Daí quando salvo já me deixa começar a cadastrar.</p>
<p>Vamos lá, quero me cadastrar aqui, &ldquo;Fabio Akita&rdquo;, telefone bla bla bla, empresa Codeminer 42. Agora vou cadastrar &ldquo;Cecil Wayne&rdquo;, o criador do dBase que depois se tornaria vice-presidente da Ashton-Tate. Finalmente, vamos cadastrar Brian Russell, que criou o compilador Clipper, pra Nantucket Corporation. Nenhuma dessas empresas existe mais.</p>
<p>A Ashton Tate foi adquirida pela Borland que depois viraria Inprise, depois Embarcadero e a divisão CodeGear que ainda mantém o Delphi, se não me engano. A Nantucket iria pra CA ou Computer Associates, que desde 2018 faz parte da Broadcom. Todo o sucesso que eles tiveram nos anos 80 e 90 desapareceram ao ponto que hoje ninguém mais lembra. É um bom lembrete que nada dura pra sempre.</p>
<p>Enfim. Terminei de cadastrar meus contatos. Posso salvar e sair com control + end. Agora estou na tal linha de comando do interpretador. Posso abrir o arquivo do banco fazendo &ldquo;use contatos&rdquo;, digitar o comando LIST e olha só, lista o que acabei de registrar. Esse é o avô de todos os CRUDs, o CRUD original no mundo dos microcomputadores. Não o primeiro, mas possivelmente um dos mais populares.</p>
<p>O formato de arquivo que Cecil inventou foi o famoso DBF, literalmente database file. Quando eu era novo, antes de pular pra tecnologias mais novas como SQL Server da Microsoft, na minha cabeça &ldquo;banco de dados&rdquo; é o que você chama de tabela. O dBase gravava uma tabela por arquivo e chamava isso de banco de dados. Hoje, banco de dados é um servidor com conjuntos de tabelas num único arquivão. No fim dos anos 80, banco de dados, pra mim, era um arquivo com uma tabela.</p>
<p>Esse arquivo tem um espaço reservado nos primeiros endereços pra metadados, informações como o total de registros e coisas assim. Mas os registros em si tem todos o mesmo tamanho fixo. No exemplo dos contatos, são 100 bytes pro nome, 13 pro telefone e 100 pra empresa, então cada registro sempre vai ocupar 213 bytes. Basta ir pulando de 213 em 213 bytes pra ir avançando pro próximo registro.</p>
<p>Como programador você precisa entender essa estrutura. Digamos que eu queira encontrar o telefone do Brian. Como faz? Como é anos 80, vamos começar do jeito mais simples, ou seja, tosco. Podemos escrever um programinha besta, que vou chamar de &ldquo;list.prg&rdquo;. Abro o arquivo com &ldquo;USE&rdquo;, limpo a tela e começo a fazer um loop checando que não estamos no fim do arquivo, end-of-file ou EOF como tá aqui.</p>
<p>Daí fico comparando o campo nome do registro atual com &ldquo;Brian&rdquo;. E não acharia nada. Isso porque preciso procurar pelo nome exato, então &ldquo;Brian Russell&rdquo;. Claro, posso só pesquisar as primeiras letras também, mas é uma operação a mais, por agora vou simplificar. Da linha de comando do DOS podemos chamar o dbase passando esse programa.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>USE contatos
CLEAR
DO WHILE .NOT. EOF()
	IF NOME = &#39;Brian Russell&#39;
		@ 10,10 SAY &#39;Empresa:&#39; GET EMPRESA
		EXIT
	ENDIF
	SKIP
ENDDO</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Nesse exemplo idiota, começa pesquisando o primeiro registro, que é Fabio. Não achou e não é fim de arquivo, então vai pro próximo com o comando <code>SKIP</code>, que é Cecil, ainda não é fim de arquivo, então vai pro próximo e acha o Brian, daí é só pegar o campo empresa, imprimir na tela com esse comando <code>SAY</code> e sair do loop com <code>EXIT</code>.</p>
<p>Esse é o pior caso: onde o que quero achar é o último registro. Imagine um arquivo muito maior, com mil registros. Precisaria ir um a um mil vezes. Todas as vezes. Por isso, esse é jeito mais tosco e mais primitivo de procura num banco de dados. Agora que entendeu isso, prometa que nunca vai fazer desse jeito.</p>
<p>Num computador moderno, esse programa vasculhando milhares de registros ia demorar menos de um segundo. Mas nos anos 80 poderia levar alguns segundos, dependendo de quantos registros tenho. Mas claro, nem nos anos 80 a gente era ruim de fazer desse jeito tosco.</p>
<p>Em dBase mesmo dava pra criar um segundo arquivo, um índice. Basta fazer &ldquo;INDEX ON NOME TO INDNOME&rdquo; e vai criar um arquivo separado de índice com extensão &ldquo;NDX&rdquo;. E podemos abrir o banco de dados com &ldquo;USE contatos INDEX INDNOME&rdquo;. Como já tá aberto podemos só fazer &ldquo;SET INDEX TO INDNOME&rdquo;. É assim que podemos ficar mudando de índices dependendo de que campo queremos procurar.</p>
<p>Eu digitei errado o nome da empresa do Cecil, vamos usar <code>SEEK 'Cecil Wayne'</code> e fazer <code>REPLACE EMPRESA WITH 'Ashton Tate'</code>. Pronto, é assim que procuramos e depois modificamos um registro. O comando Seek vai usar o índice, em vez de fazer o loop com comparação como fiz antes. Aliás, esse comando REPLACE serve pra mudar dados dos campos do registro atual. Com SEEK podemos encontrar um registro, mas pra criar um novo, teríamos que fazer &ldquo;APPEND BLANK&rdquo; antes, pra &ldquo;apendar&rdquo;, anexar um registro vazio no fim do arquivo, daí o REPLACE gravaria os dados nesse registro novo. Esse é o básico.</p>
<p>Esse tipo de índice eu já expliquei no episódio de Árvores e também nos de armazenamento. É uma variante de uma B-Tree. Lembram que falei que árvores são as estruturas mais importantes na computação? O arquivo DBF de banco de dados é mais ou menos como se fosse um array de structs em disco, mas um arquivo NDX de índice é uma estrutura de árvore B. Daí, em vez do tempo de procura ser linear em força bruta com loop um a um, com o índice passa a ser logarítmico, que é uma ordem de grandeza mais rápido e eficiente.</p>
<p>Ou seja, no meu programa de procura tosca via loop e comparação, se cada comparação e skip levasse, digamos, 1 milissegundo, e eu tivesse 10 mil registros e o que eu procuro estivesse no último registro, então no pior caso levaria 10 mil milissegundos, 10 segundo inteiro. Mas se eu usar um índice em B-Tree, com complexidade logarítmica, levaria talvez pouco mais de 10 milissegundos.</p>
<p>Mesmo se eu dobrasse a quantidade de registros, o tempo de pesquisa não iria aumentar muito mais que isso, dado a natureza logarítmica. Dependendo do que estou fazendo, o ganho de performance poderia ser 100 vezes ou mais usando o índice correto. Por isso falamos tanto no mundo de bancos pra sempre garantir que tem um índice correto preparado.</p>
<p>Mas e se eu quisesse agora procurar pelo telefone? Ou pelo nome da empresa? Bom, podemos criar um índice pra cada campo, como <code>INDEX ON TELEFONE TO INDFONE</code> e <code>INDEX ON EMPRESA TO INDEMPR</code>. Note como o nome do índice eu tive que truncar, porque em MS-DOS nomes de arquivos podiam ter no máximo 8 letras. Era uma limitação do FAT16 antigo. O sistema de arquivos FAT que você usa pra formatar SD Cards pra câmera é ou FAT32 ou o mais moderno ExFAT, e não o FAT16 antigo. São compatíveis, mas com limites muito maiores, por isso dá pra ter nomes de arquivos longos normalmente.</p>
<p>Voltando pro DOS podemos listar os arquivos criados. Como disse antes, cada registro vai ocupar aproximadamente 213 bytes. Tenho 3 registros, então teríamos pelo menos 639 bytes. No caso o formato DBF grava mais coisas como cabeçalho de metadados, por isso faz sentido ser 986 bytes. Mas veja cada arquivo de índice. 1 kilobyte cada. Acho que é o tamanho mínimo pra um índice.</p>
<p>Eu fiz um programa besta pra adicionar mais registros falsos só pra aumentar o tamanho dos arquivos. Olha a listagem aqui. A chatice de uma linguagem tão rudimentar quanto dBase III é que ele sequer tem função pra gerar números aleatórios nem tem coisas triviais como calcular o módulo de um número. Então tive que fazer um gerador de números usando só segundos. Não tem nem função pra pegar milissegundos então tenho muitas letras repetidas, mas tudo bem. Olha só como tive que fazer pra calcular o módulo na mão. Daí vou pegando letras da tabela ASCII e concatenando. Super rudimentar.</p>
<p>Gerando 40 novos registros e reindexando, vamos ver o tamanho dos arquivos. O banco de dados, a tabela de contatos em si, com 43 registros agora ficou com quase 10 kilobytes, Pouco mais que 240 bytes por registro, em média. Já os índices, o de nome ficou com quase 8 kilobytes e telefone que é um campo menor, ficou com pouco mais de 2 kilobytes. Empresa é o mesmo tamanho de Nome então ficou com uns 8 kilobytes também.</p>
<p>Pense por um segundo. Os arquivos de índice estão ocupando quase o dobro do espaço da tabela em si. Muita gente pode não entender isso, mas índices ocupam espaço, e não é pouco espaço. Mais importante que isso. Num banco moderno, digamos que a operação de inserção na tabela de um novo registro leve 1 milissegundo. Mas agora digamos que temos esses 3 índices. E atualizar cada um dos índices leve mais 1 milissegundo cada. Agora é um total de 4 milissegundos pra inserir um novo registro e atualizar os índices. O tempo de inserção ficaria 4 vezes maior.</p>
<p>Índices é uma faca de dois gumes. Por um lado podemos acelerar a pesquisa em 100 vezes, talvez mais, mas por outro lado, operações como inserção, atualização ou deleção vão demorar duas vezes mais, quatro vezes mais, dependendo da quantidade de índices que tivermos. Entenderam?</p>
<p>Aliás, índices em dBase, ou mesmo em Clipper, eram super instáveis. Vira e mexe ficavam desatualizados e por isso todo programa que fazíamos naquela época colocávamos no menu uma opção de manutenção. Ensinávamos o operador do cliente rodar de vez em quando. Era a operação pra reindexar tudo do zero, pra garantir que a pesquisa ia funcionar. Quanto maior a tabela, mais demorado ficava reindexar. Felizmente bancos de dados modernos são bem mais estáveis pra atualizar os índices e fazem isso sem precisar de intervenção manual, na maior parte do tempo.</p>
<p>Mas não é só isso. Até agora só falei sobre adicionar novos registros nesse arquivo. Isso é fácil, é só ir apendando, concatenando os bytes do registro no fim do arquivo. Atualizar, também não é tão difícil. Só dar &ldquo;SEEK&rdquo; pra encontrar o registro pelo índice e dar &ldquo;REPLACE&rdquo; pra substituir com o dado novo por cima do campo desejado. Mas e pra apagar? O que acontece?</p>
<p>Pensa um Array em memória. É uma sequência contínua. Se você quiser deletar um elemento no meio, não tem outro jeito. Precisa criar um novo array e copiar todos os elementos de novo, pra não ficar buraco no meio. Por isso que em memória usamos outras estruturas como listas ligadas. Daí é só mudar o ponteiro do elemento anterior pra apontar pro elemento seguinte. Mas listas ligadas são super ineficientes comparados com arrays, são mais difíceis de navegar, porque não tem endereços contínuos, então não tem como só calcular o offset e ir direto pro elemento no fim, tem que ir navegando ponteiro a ponteiro. É muito lento.</p>
<p>Um arquivo DBF é parecido com um array: não tem como apagar um registro no meio. O que acontece é que cada registro tem um bit separado só pra dizer se é válido ou não. Quando manda apagar, ele só grava esse bit de zero pra um, daí quando tentamos dar SEEK ou navegar pra frente com SKIP, ele vai ignorar se esse bit estiver marcado como apagado. Isso torna a operação simples com um trade-off: o espaço no disco vai continuar sendo ocupado, o arquivo não diminui de tamanho.</p>
<p>Por isso que além de reindexação, que é regerar os arquivos de índice, na tal opção de manutenção, também tinha outro comando: &ldquo;PACK&rdquo;. O que o PACK vai fazer é a mesma coisa que com um array: gerar um novo arquivo DBF do zero e copiar todos os registros não apagados pra lá, e no final apagar o arquivo antigo. Esse é o único jeito de recuperar espaço do disco. Mas pra isso precisava ter quase o tamanho do arquivo original de espaço livre sobrando pra fazer a operação.</p>
<p>Isso existe até hoje em bancos de dados modernos. Quem já mexeu com SQLite3 ou Postgres deve ter esbarrado no comando &ldquo;VACUUM&rdquo;, tem até a opção de AUTO VACUUM pra ele fazer isso sozinho sem termos que enviar o comando manualmente, mas em essência é a mesma ideia do comando PACK de dBase: recuperar espaço em disco e desfragmentar os dados no disco pra ficar mais eficiente e ocupando menos espaço.</p>
<p>Falando em SQLite3, ele seria o sucessor espiritual do dBase e Clipper. É provavelmente o banco de dados relacional mais usado no mundo. Está em todo lugar. Se você tem dispositivos smart com Android da vida, os aplicativos rodando dentro certamente usam SQLite3. O roteador da sua casa, sua Smart TV, sua Alexa, seu smart watch, tudo tem SQLite3. Qualquer aplicativo que precise armazenar qualquer tipo de dados, normalmente usa SQLite3.</p>
<p>A razão disso é por ser extremamente pequeno, altamente eficiente, flexível e portável. O código fonte dele é em C super limpo, que pode ser facilmente portado pra rodar em qualquer sistema operacional, seja Linux, seja Mac e pra qualquer arquitetura, seja x86 no seu PC, seja ARM na sua SmartTV ou console de videogame. Cabe tudo num único arquivo de C com poucos page down e oferece uma linguagem compatível com SQL. É muito bem feito, um dos melhores e mais úteis projetos de código aberto de todos os tempos. Se você é estudante de C, vale a pena explorar o código fonte, não é tão difícil de entender.</p>
<p>Isso me leva ao episódio onde mostrei o que seria uma introdução de como fazer uma linguagem similar a SQL e eu usei justamente um trecho da gramática de SQL do SQLite3. Só que por baixo fiz ele acessar uma estrutura em memória de Javascript. Poderia ser um arquivo DBF como de dBase e Clipper que acabei de mostrar. A diferença é que em vez de comandos como APPEND, REPLACE, USE, SKIP que mostrei até agora, poderíamos oferecer uma versão com SELECT, INSERT ou UPDATE.</p>
<p>O formato DBF é usado até hoje em muitos lugares. Um Pandas de Python tem capacidade de carregar e manipular DBF. A linguagem R pra cálculo numérico também consegue entender DBF. Linguagens como Javascript, Java, C#, todos tem bibliotecas que conseguem carregar DBF. É um formato legado que ainda é usado, por ser super simples, por ter sistemas legados em indústrias antigas. Obviamente, ninguém deveria iniciar um novo sistema dependente desse formato, pra isso existem coisas mais modernas, como o próprio SQLite3.</p>
<p>SQLite3, como dBase ou Clipper, não depende de ter um servidor de rede pra servir os dados, nem clientes complicados. Ele abre o arquivo e você manipula diretamente. Por isso é tão pequeno e eficiente. Existem opções pra oferecer SQLite3 em rede, mas o foco dele sempre foi rodar localmente. Acho que exige menos de 1 megabyte de RAM pra rodar, talvez faixa de meio megabyte, o que é ridiculamente pequeno. Muitos programas de Clipper dos anos 90 precisava mais que isso.</p>
<p>dBase, Clipper, FoxPro é o que chamamos da família de linguagens xBase, que suportam o formato DBF. Existe uma versão mais moderna de Clipper de código aberto que é o Harbour e seu fork, o xHarbour. Não sei se alguém usa, mas serviria pra portar antigos programas xBase pra rodar em plataformas mais modernas além de adicionar funcionalidades modernas como orientação a objetos e capacidade de se conectar em bancos SQL modernos. De novo, não recomendo começar nenhum novo projeto neles. Qualquer Python da vida, conectando num SQLite3 ou Postgres vai ser melhor.</p>
<p>Tecnologias dos anos 80 eram limitados pelos poucos recursos que a gente tinha. Os processadores eram super lentos comparados com hoje. Estamos falando de milhares de vezes mais lento. Os programas precisavam ser super eficientes, porque qualquer operação fora do lugar roubava recursos escassos, seja de CPU, seja de RAM. Eles precisavam rodar em menos de 640 kilobytes de RAM, em 15 megahertz ou menos. Qualquer processador porcaria de hoje em dia tem 2 ou mais núcleos, cada um com pelo menos 1 gigahertz. Olha a diferença.</p>
<p>Eu mencionei que o sistema de arquivos FAT16 do MS-DOS da época só suportava arquivos de 8 letras mais 3 de extensão. Não tinha memória suficiente pra suportar estruturas de dados maiores que isso. Mais importante: não tinha processamento sobrando pra garantir a integridade dos dados. Coisas como calcular o checksum ou fazer hash dos arquivos. Pros anos 80 seria muito pesado. Por isso disquetes em FAT16 costumavam falhar, arquivos ficavam corrompidos, dados se perdiam.</p>
<p>Mesma coisa pro formato DBF. Era comum esses arquivos se corromperem. Backup era essencial. E backup naquela época era mandar fazer uma cópia num disquete no fim do dia. Toda lojinha da esquina, que tinha um programa em Clipper pra controlar estoque, fornecedores, contas a pagar e a receber e coisas assim, sabia que precisava fazer isso. Fiz alguns programas desse tipo no começo dos anos 90.</p>
<p>No SQLite3, o formato de arquivos reflete essa diferença. Pra começar não é um arquivo append only como DBF, onde vai simplesmente gravando um registro na frente do outro e navegando via offset de endereços. Ele adota uma estrutura de árvore B, uma B-Tree. Não só no índice mas no banco de dados em si. Aliás, no caso do SQLite3, tem um único arquivo que contém várias tabelas, em vez de cada tabela ser um arquivo separado.</p>
<p>Internamente não vai alocando registros e sim páginas. Páginas são como ranges, ou intervalos de endereços. Por exemplo, a página 1 pode ser do endereço 1 até 500, página 2 do endereço 501 até 1001. Páginas contém registros, ou linhas de uma tabela. As páginas são organizadas numa árvore B e os registros são nós folhas, leaf nodes, dentro das páginas.</p>
<p>Nos episódios de árvore, de sistemas de arquivos e de SQL eu falei sobre B-Trees e suas variantes como B+Trees, que sistemas de arquivos modernos como ZFS ou BTRFS utilizam. Tudo no seu sistema operacional é alguma variante de árvores, em particular árvores B, por isso falei que era importante saber como funcionam. No caso do SQLite3 tanto o banco de dados propriamente dito, quanto seus índices, são variantes de árvores B. Elas são muito eficientes. Vamos recapitular.</p>
<p>Primeiro de tudo, o B não é de binário. Árvore binária é um tipo de árvore mas não é b-tree. O B não tem uma definição definitiva mas pense como &ldquo;balanceada&rdquo;. Uma característica importante é se auto-balancear. Todos os nós folha tem a mesma profundidade. Isso significa que não importa qual chave tá procurando, leva mais ou menos o mesmo tempo pra acessar qualquer nó. Isso é importante porque o tempo de &ldquo;seek&rdquo; ou procura num HD mecânico custa tempo. Uma árvore B minimiza a quantidade de operações de procura.</p>
<p>Árvores B tem alto fan-out, ou seja, são feitas pra cada nó interno poder ter um número grande de nós filhos, que é a ordem da árvore. Isso ajuda armazenamento em disco porque permite uma grande porção dos dados ser escrito tudo numa única página. O jeito antigo do DBF de ir alocando um registro de cada vez no fim é bem ineficiente e gera muita fragmentação. Alto fan-out significa que muitas chaves podem ser acessadas de uma só vez numa única operação de procura em disco. É melhor pegar uma chave que não precisava junto do que ficar procurando aleatoriamente por todo o disco.</p>
<p>Operações de inserção e deleção são muito eficientes. O algoritmo que mantém a árvore balanceada, e eu mostro uma versão disso no video de árvore, é super eficiente. Quando uma nova chave é inserida ou apagada a árvore é re-balanceada, pra garantir a propriedade dos nós folha terem sempre a mesma profundidade. E é muito estável mesmo com a árvore crescendo muito. Um banco de dados de SQLite3, que é super pequeno, aguenta um arquivão que pode teoricamente atingir até 280 terabytes.</p>
<p>Operações que pegam vários registros de uma só vez, tipo um SELECT WHERE da vida ficam mais eficientes, pela propriedade de vários registros estarem juntos numa mesma página. Com poucas operações de disco dá pra pegar todas elas ordenadas de uma só vez. Então, coisas como listagens e montar relatórios, tende a ser muito mais eficiente do que num DBF antigo. Tudo que você pedir que seja ordenado e sequencial, tende a ser eficiente pelas propriedades de páginas que expliquei.</p>
<p>Eu sempre ouvi que ninguém deveria tentar fazer um banco de dados do zero sem ter visto como um de verdade funciona por baixo, e esse é um dos motivos: operações em disco, especialmente mecânico, custam caro. Hoje custam caro, mas nos anos 80 ou 90 eram bem mais rudimentares. Um HD moderno, como meu IronWolf de 12 terabytes vem embutido com 256 megabytes de memória RAM. Não tinha PC nos anos 90 com tudo isso de RAM. Sem cache, as operações são mais custosas, porque realmente precisa colocar a cabeça em cima do disco de verdade.</p>
<p>Aqui estou especulando porque realmente não me lembro se era esse o caso, mas por exemplo, eu reservaria espaço no começo do disco pra colocar coisas como metadados, índices e os dados em si começariam a ser gravados depois. Como se eu tivesse uma partição C:, mais perto do centro do disco físico, onde as operações são mais rápidas do que perto das bordas dos discos. Daí numa partição D: ficariam os dados em si.</p>
<p>Aliás, acho que é por isso que antigamente a gente particionava o HD, no C: ficava o sistema operacional, pra garantir que no boot o disco trabalhe menos e seja mais rápido, daí os dados e programas ficam no D:, mais longe do centro do disco. Mesma razão de hoje em dia deixarmos o boot num SSD ou NVME, que é muito mais rápido, mas sua biblioteca do Steam colocamos num HD mecânico separado. Sempre é um trade off entre mais performance ou mais espaço.</p>
<p>E não só SQLite3, claro, qualquer banco de dados moderno, como Postgres, Oracle, SQL Server, e até bancos não relacionais, como MongoDB, Firebase, Dynamo, Cassandra. E não só bancos de dados como sistemas de arquivos como ZFS ou BTRFS que já mencionei, todos usam alguma variante de árvore B, exatamente por todas essas propriedades. Tudo que lida com gerenciamento de dados, usa árvores B.</p>
<p>Porra Akita, mas um MongoDB não grava tudo em JSON? Eu mando JSON e ele me devolve JSON, portanto JSON deve ser o formato mais eficiente, não? Sério, se você for bem iniciante eu deixo isso passar, mas se algum programador pensa assim, realmente não entende nada de programação. JSON é um formato de consumo, não um formato de armazenamento. É um formato de tradução pra facilitar a vida mas é altamente ineficiente. Eu isso no episódio que falo do código do Twitter.</p>
<p>Primeiro, não é JSON, internamente os nós filhos são gravados em BSON que ele chama de Binary JSON, é um formato binário, como Protobufs do Google. Segundo, é internamente organizado numa B+Tree, ou árvore B+, onde os valores propriamente ditos são gravados direto nos nós folha no fim da árvore, diferente de uma árvore B pura que só tem os ponteiros.</p>
<p>O objetivo todo do episódio onde eu fiz um SQL em cima de um array de javascript, foi pra demonstrar que ter SQL não descreve nada de como é o banco de dados por baixo. Mesma coisa no MongoDB: a linguagem e formato de dados, como JSON, que se usa por cima, não diz nada sobre como é a arquitetura por baixo. Tanto que um Postgres, via SQL, pode devolver os dados em formato JSON se você quiser. É só uma tradução. JSON em si não significa nada pra nenhum banco de dados. Eles só precisam escolher exportar ou importar dados nesse formato. É só um formato, como XML ou CSV ou Excel.</p>
<p>Todo formato de dados de consumo como esse vai exigir um passo de importação pro formato interno binário de verdade, ou exportação pra esse formato. Então se você faz um &ldquo;SELECT *&rdquo;
da vida num banco, primeiro ele vai puxar todos os registros no formato binário dele. Isso vai ocupar um X de memória. Daí você escolhe que quer em formato JSON, então ele vai precisar de mais um tanto Y de memória, maior do que X, pra fazer essa tradução. É um processo de serialização e desserialização, e sempre vai exigir o dobro de memória pra ter essa conveniência.</p>
<p>Por isso que comunicação de um serviço com outro serviço, deveria ser sempre utilizando protocolos binários compatíveis entre eles, por exemplo, protobufs. Evitando ao máximo fazer tradução de dados, especialmente em formato texto. O formato de dados do SQLite3 não tem nenhuma relação com ele ter capacidade de receber comandos em SQL. Não existe um banco de dados &ldquo;SQL&rdquo;, existem bancos de dados que entendem a linguagem SQL, só isso. Internamente todos são implementados de formas diferentes pra determinados cenários e casos de uso.</p>
<p>Um SQLite3 é ótimo pra usar numa SmartTV, já um Postgres ou MongoDB da vida, nem tanto. Eles foram feitos pra rodar em servidores. Existem até projetos pra rodar SQLite3 em servidores, mas de novo, não foi feito pra isso. Use a ferramenta certa no lugar certo. Só porque você consegue arrancar um parafuso da parede com um alicate, não quer dizer que deveria, acha a porra da chave correta.</p>
<p>Internamente, todo banco de dados vai ter um formato de arquivos de armazenamento como um DBF, ou mais corretamente como o formato de um SQLite3, e formato de arquivos de índice. Todos vão ser alguma variante de árvore B ou árvore B+. Além disso temos mais coisas modernas, no caso do SQLite3 vai ter um outro arquivo com nome terminando em &ldquo;wal&rdquo;, de write ahead log. Isso é outra coisa que sistemas de arquivos como FAT não tinham, mas todo sistema moderno como NTFS ou ext4 tem: um journal.</p>
<p>Pra economizar recursos, aquele comando REPLACE que eu mostrei lá no começo com dBase, escreve por cima do registro antigo no arquivo DBF diretamente. Isso é péssimo. Porque digamos que estamos fazendo uma atualização em massa em todos os registros do arquivo. Colocando o prefixo 9 em todo telefone que é de São Paulo, lá em 2012. Agora digamos que no meio dessa atualização acabou a luz e o computador reboota. Fodeu.</p>
<p>Isso era comum antigamente e significava que muito provavelmente o arquivo ficou num estado corrompido e eu perdi algum dado. Hoje em dia não se faz mais isso. Existem diversas estratégias, mas o SQLite3 escolheu um log de escrita pra frente ou write ahead log. Ele primeiro escreve as modificações nesse arquivo separado de log, depois que termina, troca um pelo outro. Bem simplificado é como se eu tivesse um arquivo chamado &ldquo;contatos&rdquo; daí tenho uma cópia dele chamado &ldquo;contatos-wal&rdquo; e as atualizações eu faço nesse segundo arquivo. Se tudo correu bem até o fim, agora troco um arquivo pelo outro.</p>
<p>É um pouco mais avançado que isso, mas só pra ter na cabeça a idéia. Se acabar a luz e o segundo arquivo estragar, ainda tem o original intacto. Além disso, se precisar fazer pesquisas, posso pesquisar no original enquanto a transação não acaba no segundo arquivo, em vez de ficar travado tendo que esperar até todas as operações acabarem. Todo banco de dados tem algum mecanismo desse tipo, mais sofisticado pra ter o menor tempo de trava quanto possível e garantir que nunca nenhum dado vai ser corrompido.</p>
<p>É um tipo de mecanismo de COW ou Copy on Write, como o sistema de arquivos BTRFS também tem em Linux. Por isso que é bem incomum hoje em dia perder dados. Mesmo o Windows tem NTFS, que protege razoavelmente bem. Qualquer Linux mais vagabundo usa pelo menos ext4, que tem suporte a journaling. Pra perder dados hoje, você precisa estar usando um hardware muito do vagabundo, como um SD card de câmera.</p>
<p>SD card é um hardware de armazenamento que foi feito pra ter grande capacidade e ser super barato. Ele foi feito pra tirar fotos, gravar videos e logo em seguida transferir pra um computador. Não foi feito pra deixar lá guardado pra sempre. Não é confiável e podemos perder dados do nada, especialmente nos modelos mais baratos. SSDs baratos de Aliexpress, mesma coisa, mas nesse caso porque são fraudes mesmo, fabricados pra falhar. Cuidado com isso.</p>
<p>Outra característica que bancos relacionais trouxeram foi esse conceito de transação e ACID, de atomicidade na operações. Ou todas acabam com sucesso ou nenhuma acaba. De consistência, por exemplo, regras de unicidade, chaves estrangeiras, e tudo mais. Os dados precisam sempre seguir essas regras, sem exceção. Isolamento, ou seja, se um conjunto de transações roda em paralelo, o resultado tem que ser o mesmo se eles tivessem rodado sequencialmente. O banco não pode ficar num estado inconsistente. E durabilidade, se a transação diz que gravou o dado, podemos acreditar que realmente está fisicamente no disco.</p>
<p>Caso um erro aconteça, como a famosa falta de luz, o banco precisa voltar pro último estado consistente conhecido. Nunca pode continuar operando num estado inconsistente. Cada banco implementa isso de formas diferentes, mas no geral significa que podemos confiar nos dados num banco de dados relacional. Seja um MySQL ou Posgres ou SQL Server, no geral, implementam bem essas características. Mesmo o pequeno SQLite3 faz isso. Um dos jeitos mais toscos é simplesmente bloquear o arquivo todo pra cada transação, esperar completar com sucesso e só aí liberar pra outras operações. Mas claro, isso seria altamente ineficiente.</p>
<p>Mas era assim que bancos de dados xBase, DBF funcionavam em rede nos anos 90 quando começamos a usar placas de rede em MS-DOS e fazer pequenas redes usando ferramentas da antiga Novell ou o Windows 3.1 for Workgroups ou o venerado OS/2 da IBM. Na prática, um computador era elegido como um servidor de arquivos, esses arquivos DBF e NDX eram compartilhados em rede, e todos os outros computadores na rede enxergavam os mesmos arquivos.</p>
<p>Cada computador tinha a mesma versão do programa em dBase ou Clipper ou FoxPro instalado. Se algum tivesse uma versão mais antiga, era caos. A consistência, as regras, dependiam totalmente da versão do programa instalado em cada máquina. A atomicidade era mais ou menos garantida por um sistema de lock. Quando um programa quisesse inserir um registro novo, ele travava o acesso àquele arquivo. Se um segundo computador tentasse ler, recebia um erro que o arquivo tava ocupado, e precisava esperar.</p>
<p>Isso nem sempre funcionava. E se o arquivo DBF, sozinho numa única máquina tendia a se corromper, porque não existia nenhum sistema de proteçõ como journals, imagine em rede. Corromper arquivos era rotina. Experimenta esquecer de fazer backup no fim do dia, certeza que no dia seguinte ia dar pau e corromper alguma coisa. Sistemas de arquivos em rede como Novell era uma puta gambiarra feita em cima do DOS, com tecnologias super imaturas.</p>
<p>Hoje usamos protocolos de internet como TCP/IP, mas naquela época usávamos protocolos mais rudimentares como IPX/SPX e NetBIOS da Microsoft. Verdadeiras porcarias. E mesmo a tecnologia de redes em si, hoje usamos Ethernet de 1 gigabit pra cima, antigamente eram cabos coaxiais que nem de TV a cabo, que male male, faziam 10 megabits. Perdiam pacotes, corrompiam dados e assim vai.</p>
<p>Com a migração de MS-DOS pra Windows e da arquitetura de 16 bits pra 32 bits, ganhamos até 4 gigabytes de RAM pra trabalhar, sem precisar fazer gambiarras de memória em DOS como eu explico no episódio dos 640 kilobytes. E com isso, em vez de cada programa em cada máquina da rede, tentar acessar diretamente os arquivos de dados, eles passaram a conversar com um software servidor. Dessa forma, só um software tinha acesso direto aos arquivos e todos os outros, programas clientes, conversavam com esse servidor usando algum protocolo, com alguma linguagem, no caso o SQL.</p>
<p>Essa foi a era de programas feitos em Visual Basic ou Delphi, conversando com bancos de dados servidores mais modernos como SQL Server, que tinha T-SQL, ou Oracle, que tinha PL/SQL ou alguns mais de nicho como o Interbase da Borland, que era o padrão pra quem usava Delphi. Em vez de cada programa cliente ter que controlar a integridade dos arquivos, melhor terceirizar isso pra um servidor. Os clientes só falam &ldquo;ow, grava isso pra mim&rdquo; ou &ldquo;ow, pesquisa isso pra mim&rdquo; e o servidor se vira. Isso aumentou muito não só a escalabilidade mas também a estabilidade. Se um cliente manda uma operação não suportada, o servidor pode só recusar, em vez de tentar executar e corromper arquivos.</p>
<p>SQLite3, oficialmente não tem um servidor. Mas tem gente tentando fazer um sei lá porque. Tem um projeto chamado ValentinaDB que faz isso. Não vejo nenhuma vantagem. Se você quer um banco de dados SQL, formato cliente-servidor, use um de verdade, que é maduro, como MySQL ou Postgres. Use SQLite3 pra programas que precisam de dados localmente, como seu roteador de internet que já dei de exemplo.</p>
<p>Quando temos o formato de um servidor, temos algumas vantagens e algumas desvantagens. A primeira é que a velocidade vai depender da qualidade da rede. Tudo que roda localmente sempre é mais rápido. Em rede você precisa pegar o resultado, quebrar em pacotes, rotear esses pacotes, ir recebendo, processando um buffer, e só aí vai ter os dados.</p>
<p>Podemos escolher ir recebendo e já processando os dados localmente, ou podemos escolher esperar receber a resposta toda e só depois fazer alguma coisa. É a diferença de receber tudo de uma vez ou usar técnicas de streaming. E aí depende da sua pesquisa. Se for tosca e mal feita, e mandar tipo um &ldquo;SELECT *&rdquo; pra uma tabela de 10 gigabytes, sem usar streaming, boa sorte, vai ocupar toda a memória da sua máquina e mais o swap em disco.</p>
<p>Isso é o que iniciantes tem mais dificuldade de entender: os dados que pedem precisam ir pra algum lugar. Nada é de graça em computação. O trabalho principal de um programador é justamente administrar os recursos da máquina da maneira mais eficiente possível. Por exemplo, digamos que preciso montar um relatório em PDF de todas as vendas do mês. Vou fazer tipo um &ldquo;SELECT * FROM ORDERS WHERE created_at &gt; date() - 30&rdquo;. Essa é uma pesquisa rudimentar que filtra todas as ordens criadas nos últimos 30 dias. Faz de conta que vai devolver 500 ordens.</p>
<p>Digamos que cada uma dessas linhas consuma 500 bytes, só meio kilobyte. Se eu puxar tudo de uma vez do banco e colocar num array em memória, vai ocupar 250 megabytes. É bastante coisa. Daí abrimos uma biblioteca qualquer pra montar PDF e converter essas linhas em um documento PDF em memória. Pra simplificar o exemplo, digamos que cada linha no PDF também use 500 bytes. Significa que no fim da operação, vou estar consumindo 500 megabytes na memória, meio gigabyte, metade com os dados que vieram do banco e metade com o PDF.</p>
<p>Qual o jeito certo? Depende, talvez seja esse mesmo. Existem técnicas se quiser otimizar. Um dos jeitos é escolhendo uma biblioteca de PDF que suporte escrever direto pro disco em vez de acumular tudo em memória. Por exemplo, em Java tem a biblioteca PDFBox da Apache. Podemos ir salvando incrementalmente uma página de cada vez pro disco. Digamos que cada página tenha 20 ordens, então só vou precisar de aproximadamente 10 megabytes na memória por vez.</p>
<p>Além disso podemos ir recebendo uma linha de cada vez do banco, e montar uma linha no PDF e descartar essa memória, daí recebo a próxima linha e faço a mesma coisa, sem acumular tudo. Em resumo, significa que de memória de trabalho precisaria, no máximo, ter uns 10 megabytes pra montar uma página de PDF e mais meio megabyte por linha que recebo do servidor de banco de dados, um total de 10 mega e meio.</p>
<p>Aí não importa se o relatório vai ter 100 ordens ou 1000 ordens, sempre vou usar o máximo de 10 mega e meio de cada vez, e não 500 megabytes pra 500 ordens ou 1 gigabyte se for mês de natal e tiver 1000 ordens. Esse é seu trabalho como programador: encontrar o teto máximo que realmente precisa e limitar o uso de recursos. Só assim é possível escalar. O jeito amador é ir usando cada vez mais memória à medida que os dados vão aumentando de volume. Sempre pense em jeitos de usar um teto fixo de memória e processamento, independente de quantos dados vai processar.</p>
<p>Quando falamos que é importante entender SQL direito, primeiro é pra aprender a fazer queries ou pesquisas, SELECTs que devolvam a menor quantidade de dados quanto possível, porque cada dado extra que vier e não usarmos, vai desperdiçando memória e processamento. Mas também precisamos entender algumas noções como, se vamos escrever ou atualizar dados, é melhor mandar os comandos todos de uma só vez do que mandar um de cada vez, esperar um por um terminar e confirmar antes de mandar o próximo.</p>
<p>Aquele exemplo que falei de adicionar o número 9 na frente de todo número de telefone de São Paulo em 2012. Digamos que tenha 100 mil usuários na minha tabela. O jeito mais tosco possível é fazer um programa que conecta no banco e primeiro faz um &ldquo;SELECT *&rdquo; e devolve todo mundo pro seu programa. De novo, se cada registro ocupa meio kilobyte e você deu asterisco, mandou voltar todos os campos da tabela, são 50 mil megabytes, 50 gigabytes de dados. Estão entendendo porque isso de puxar tudo pra memória é ruim? É o jeito mais fácil de pensar primeiro, mas é o pior jeito.</p>
<p>Digamos que você tá num servidor parrudo na Amazon, ou num PC igual o meu com 64 gigabytes, e depois de um bom tempo,  carregou a tabela inteira em memória. Agora você faz um loop nessa lista e manda dar um UPDATE no servidor pra cada registro. Um UPDATE em uma transação separada, 100 mil vezes. São 100 mil updates, 100 mil alterações individuais de índice, 100 mil commits, e 100 mil esperas de retorno OK do servidor. Lá se vão mais algumas horas esperando.</p>
<p>Qual o jeito certo? Se não ficou óbvio, não precisa dar SELECT de nada. Só fazer um único comando de UPDATE. Algo como &ldquo;UPDATE SET TELEFONE = &ldquo;9&rdquo; + TELEFONE WHERE CITY = &lsquo;Sao Paulo&rsquo; AND bla bla&rdquo; . Um único comando que vai mexer em todos os milhares de registros tudo de uma só vez. Com uma única conexão de rede, um único commit, uma única espera. Não precisa puxar todo mundo pra memória, depois fazer update um a um separado. Entenda esse conceito. A melhor operação trás zero dados do servidor e opera tudo lá.</p>
<p>Do jeito tosco, o programa cliente, digamos sua aplicação web ou mobile, mandou individualmente um comando de update pra cada usuários, esperou a resposta e mandou o próximo, 100 mil vezes. Do jeito certo, seu programa só se conectou uma vez e só mandou um comando, e afetou os mesmos 100 mil registros. A diferença de tempo aqui vai ser na ordem de centenas ou milhares de vezes mais rápido e usando ordens de grandeza menos recursos de memória e processamento. Isso é entender SQL.</p>
<p>Programadores iniciantes aprendendo hoje não tem desculpa. O pessoal das antigas que fazia dBase, até consigo entender, porque naquele formato rudimentar de DBF, não tinha outra forma. Tinha que iniciar um loop &ldquo;DO WHILE&rdquo; fazer uma condição com &ldquo;IF&rdquo; e aí fazer &ldquo;REPLACE&rdquo; no registro atual, dar &ldquo;SKIP&rdquo; pra pular pro próximo e repetir 100 mil vezes. Não tinha um servidor que fazia isso sozinho pra gente.</p>
<p>Mas pelo menos naquela época estava tudo rodando localmente na mesma máquina, modificando diretamente o arquivo. É o que o servidor de SQL vai ter que fazer no lado dele. Mas um cliente de banco de dados não tem que fazer loop nenhum se comunicando com servidor registro a registro, quase nunca. Se você tem um loop pra processar dados vindos do servidor de banco de dados, provavelmente está errado. No mínimo é um red flag que precisa ser revisto. Pelo menos lembre disso como regra: se tem um loop, está errado, pense de novo.</p>
<p>Outra coisa que o formato antigo não permitia fazer e você tem que aprender como fazer no SQL são constraints. As tais regras de consistência. Por exemplo, um campo que nunca pode ter valor vazio. Ou um campo que é uma chave estrangeira, um campo numa tabela com o ID primário de outro registro em outra tabela. Chaves estrangeiras garantem que você só está escrevendo IDs que realmente existem. No formato DBF não existia isso, você como programador precisava garantir essas regras manualmente, no código, com um monte de IFs pra lá e pra cá. Em SQL, se tem um monte de IFs, provavelmente deveriam ser constraints.</p>
<p>No mundo moderno de cliente e servidor, significa que muitas vezes essas regras vão ser duplicadas, no cliente pra poder mostrar pro usuário que ele digitou dados inválidos, por exemplo, e no servidor, pra garantir que nas tabelas só tenha dados realmente corretos. Muita gente acha essa redundância ruim, mas eu acho o contrário. Não existe nada pior que dado inconsistente. É melhor checar duas vezes do que não checar. Checagens de consistência que só existem no front-end, e no lado do banco de dados aceita tudo e não checa nada, é a maior porcaria de todas as porcarias. Sempre esses bancos de dados estão em estado inconsistente e sempre vai dar problema.</p>
<p>Crie a tabela no schema com as constraints e validações todas, validação de tipos de dados, validação de tamanho, validação se pode ser vazio ou não, validação de chaves, tudo. Daí faça uma classe do lado cliente que reflete as tabelas usando um ORM, como Hibernate em Java ou um model em Sequelize no Javascript ou model de Django em Python, não importa, repita as mesmas validações. Daí faça um componente em React pro front-end.</p>
<p>Repita as mesmas validações. Puuuts, mas vai repetir 3 vezes? Sim, repita 3 vezes, e faça testes unitários pra cada uma delas e teste de integração que cheque todas ao mesmo tempo. Esse é o seu trabalho. É que nem médico achar que lavar mão toda hora antes da cirurgia é perda de tempo. &ldquo;Ah, de novo? Eu já lavei de manhã, não precisa de novo&rdquo;. Lave de novo.</p>
<p>Beleza, tem que estudar SQL. Não só saber sintaxe de SQL, mas o  uso de recursos e processamento no servidor. E como otimizar. Nenhum ORM, biblioteca ou framework vai fazer isso pra você. Na verdade, é muito fácil fazer o jeito errado assumindo que o framework vai cuidar de tudo sozinho. Muitos tutoriais, no intuito de serem didáticos, vão te ensinar o jeito menos eficiente. Porque o jeito mais eficiente costuma ser mais difícil de explicar e demonstrar.</p>
<p>E quando você testa com poucos registros, 10, 100, mil, não faz  nenhuma diferença. Só quando realmente coloca em produção que vai entender o impacto. Essa é a diferença de crianças e adultos: quem já teve a experiência de perder dados de cliente ou ficar tudo lento que a loja pára de funcionar. Só aí que entende &ldquo;ahhhh, eu tava entendendo errado&rdquo;. Pois é. Tente evitar ter que chegar nesse ponto. Nem todo mundo precisa enfiar o dedo na tomada pra saber que toma choque.</p>
<p>Num sistema grande de verdade, numa equipe com vários desenvolvedores, é difícil de checar se todo código foi bem feito ou não. E mesmo sendo experiente, na pressão alguém deixa passar um código que vai usar memória sem precisar ou que vai deixar tudo lento porque botou um loop num lugar que não deveria. Por isso que todo sistema web em produção precisa ser monitorado. Existem diversas ferramentas de monitoramento, seja usando os antigos Nagios ou Zabbix ou serviços mais modernos como Splunk, Datadog, AppOptics, AppDynamics, Dynatrace, Sentry.io, Instana, Azure Monitor e vários outros. Não tem desculpa não usar pelo menos um.</p>
<p>Honestamente eu não sei dizer qual é o melhor hoje em dia, mas na dúvida, sempre usei e continuo recomendando no mínimo instalar o New Relic RPM. Alguns deles são passivos, com os antigos Nagios ou Logtail, porque medem coisas fora da aplicação, como uso de CPU, memória, análise dos arquivos de log. Mas os melhores exigem que você instale uma biblioteca que age como um agente dentro da aplicação. Como tem acesso à memória da aplicação, dá pra fazer análises muito melhores.</p>
<p>Sem entrar em detalhes, podemos avaliar logo depois de um deployment em produção, e ver se uso de memória aumentou e fica constante, por exemplo, ou se continua lentamente subindo até uma hora crashear. Ou podemos monitorar quando uma campanha de marketing entra no ar e vem um volume maior de tráfego. O aumento do uso de recursos está proporcional? Mais importante, podemos avaliar quais partes do código costumam consumir mais recursos.</p>
<p>Com isso podemos focar em melhorias bem mais precisas só nas partes que realmente precisa. Em vez de ficar no achismo de mexer no código sem saber quais reais impactos está causando. Depois que uma aplicação entra em produção, toda manutenção deve ser feita ao redor de métricas. Sempre medir o antes e o depois. Se o objetivo for consumir menos infraestrutura, sempre atacar os principais ofensores segundo as métricas. Se você não consegue medir, certamente não consegue consertar.</p>
<p>No lado do banco de dados, nenhum consegue crescer infinitamente. Como disse, pro caso geral de empresas pequenas ou médias, leva muito tempo pra começar a entrar no território de Big Data. Dezenas ou centenas de terabytes de dados. Um banco de dados relacional tradicional costuma ter os seguintes problemas de escalabilidade:</p>
<p>Primeiro, as tabelas simplesmente estão grandes demais. E quando eu digo grandes, quero dizer centenas de gigabytes, ou terabytes. Por mais que você tenha índices, lembra o que mostrei no caso do DBF e seus índices NDX? O arquivo de índice vai crescendo também. Quanto mais dados for inserindo, maior vai ficando os seus índices também. E vai chegar uma hora que pesquisar índices tão pesados vai ser lento.</p>
<p>A primeira estratégia pra lidar com esse problema se chama &ldquo;arquivamento&rdquo;. O certo é ter ou um segundo servidor de banco de dados usado só pra pesquisas e relatórios, mas fora do acesso pra usuários. Digamos que você tenha um ecommerce. Se tiver sucesso e vender muito, sua tabela de ordens vai ter milhões de vendas. Mas pensa: pra que serve dados de ordens antigas? Ordens de usuários que cancelaram contas ou que não entram faz muito tempo? Mova pra um segundo banco de dados.</p>
<p>Assim você tem um banco de dados com dados que realmente seus usuários precisam, mas sem os dados que ninguém nunca acessa. Digamos que sua empresa é uma multi-nacional com dezenas de filiais, dezenas de milhares de funcionários e seu sistema controla tudo de RH: folha de pagamento, benefícios, férias, documentação, e tudo mais. Pra que tem dados de funcionários de 5 anos atrás? Arquive. Mova pra um segundo banco de dados, ou mova pra um backup frio mesmo, como fita ou pra um AWS Glacier da vida, que é tipo um S3 mais barato.</p>
<p>Em qualquer empresa média ou grande, com sistemas de controle internos, vai ter tabelas com dados antigos que não precisam estar lá. É uma prática que de tempos em tempos, todo ano, talvez todo semestre, se faça essa manutenção de limpeza: arquivar, mover dados mortos, pra outro lugar que se acessa menos. Não é apagar. É mover. Nunca é uma boa idéia apagar nada.</p>
<p>Nunca se sabe quando vai precisar, especialmente sua área jurídica. Mantenha dados no mínimo em backup, por no mínimo 5 anos, por causa da lei. A grande maioria das pequenas empresas peca em sequer ter um backup que funciona, quanto mais se preocupar com arquivamento. De novo, não é pra pequenos isso. Pequenos tem que se preocupar com o básico: ter um backup, pra começar.</p>
<p>Mas aí começamos a entrar no território de unicórnios. Uber, Netflix, iFood, MercadoLivre, e várias tech startups que geram toneladas de dados. Digamos que é um iFood da vida. Não só vai ter informações básicas como cadastro do usuário e tabela de pedidos simples, mas vai ter mais dados como quem foi o motoqueiro que fez cada entrega, que rotas usou, que contatos foram realizados durante o percurso, o usuário abriu chat, o que ele falou? É uma granularidade e volume muito maior de dados. Cada pequeno pedido gera megabytes de dados pra depois serem usados pra inteligência, métricas de como melhorar a experiência e tudo mais.</p>
<p>Empresas assim geram gigabytes de dados por dia. O problema não é só o volume de dados, mas a velocidade em que são gerados. Aqui é o território de Data Warehouses, Data Lakes e coisas assim. Em nenhuma dessas empresas vai ter um único servidor de banco de dados com tudo. Sempre vai ter múltiplos bancos de dados, e certamente de vários tipos diferentes pra cada caso de uso diferente.</p>
<p>Simplificando bastante. Do lado do usuários que usa o app, faz pedidos, acompanha a entrega e tudo mais, cada usuário vai gerar dezenas de requisições pros servidores de um iFood ou Uber da vida. As pesquisas sendo feitas precisam ser rápidas. Estamos falando de centenas de milhares de usuários acessando a plataforma ao mesmo tempo. Por mais parrudo que seja um servidor, nenhum aguentaria. Precisa distribuir essa carga em múltiplos servidores.</p>
<p>Quando se instala múltiplos servidores de aplicação, eles não podem todos acessar um único servidor de banco de dados. Iria gerar uma fila enorme e muita espera, muita latência entre requisições. O problema de bancos de dados relacionais como Posgres ou SQL Server é que foram originalmente feitos pra funcionar num único servidor parrudo. A gente escalava o serviço melhorando o hardware da máquina, colocando mais RAM ou colocando uma CPU maior. Eles não foram pensados pra funcionar em múltiplos servidores.</p>
<p>Hoje em dia fazemos um meio do caminho. O maior volume de operações costuma ser pesquisa. Como pesquisa de cardápio. Enquanto faz isso não está gravando nada, só pesquisando. Todos os serviços de bancos de dados SaaS modernos conseguem criar servidores réplica, cópia da original, automaticamente. Assim os servidores de aplicação web conseguem fazer a pesquisa em qualquer uma das réplicas, evitando que se crie gargalo só em um.</p>
<p>Mais do que isso, hoje em dia, um servidor de aplicação vai primeiro fazer a pesquisa num servidor de cache, como um Memcache ou Redis. Se usar uma AWS temos Amazon ElastiCache, que é basicamente Memcache ou Redis como serviço. A aplicação primeiro checa se tem o cardápio do restaurante no cache, se não tiver, aí pesquisa via SQL no banco de dados, grava no cache e serve de volta pro usuário. Muito da carga de pesquisa cai em cima de alguma solução de cache. É impossível criar serviços grandes como um iFood ou MercadoLivre sem ter um volume grande de cache.</p>
<p>Temos dois problemas com isso. O primeiro é que apesar de ser razoavelmente simples de criar servidores de réplica, é difícil de ter múltiplos servidores que aguentem escritas. Operações como INSERT, UPDATE ou DELETE acontecem num único servidor principal. Todas as outras réplicas costumam ser read-only, somente de leitura. Existe um pequeno delay, uma pequena latência, entre a escrita completar no servidor principal e as réplicas serem atualizadas, mas isso costuma valer a pena.</p>
<p>Todo ecommerce da vida, tem essa arquitetura de servidor de banco de dados mestre principal pra escritas, várias réplicas somente de leitura e muito cache, aliado à uma estratégia de jobs assíncronos, com Apache Kafka da vida. Estratégias que já expliquei no episódio do Ingresso.com, do Twitch e do Twitter, depois revejam. Mas mesmo assim, ainda tem casos especiais.</p>
<p>Digamos que temos um serviço de analytics, como um Google Analytics, capturando cada visualização, cada clique, toda operação que usuários estão fazendo em diversos apps, diversos sites, pra gerar monitoramento em tempo real, relatórios periódicos com informações de cada usuário.</p>
<p>Ou imagine que fazemos o software que captura informações vindas de Apple Watch ou Galaxy Watch. Informações de batimentos cardíacos, que cada usuário te manda 1 vez a cada 2 minutos. Apple e Samsung tem milhões de usuários, consegue imaginar esse volume de dados?</p>
<p>Ou imagine uma corretora de criptomoedas como uma Binance, com milhares de transações acontecendo a cada minuto, milhares de pessoas comprando e vendendo sem parar. Ou imagine um servidor de games com Call of Duty ou Fortnite, onde se captura informações de cada ação de cada jogador, cada movimento, cada tiro, cada pontuação. Jogadores do mundo inteiro.</p>
<p>Estamos falando de gigabytes, sendo gerados por minuto. É muito dado. E em cima desses dados, queremos gerar compilados, como o relatório da sua saúde na última hora, ou nas últimas 24 horas, ou do mês. Pra isso tem que ordenar, agrupar, processar e sumarizar gigabytes de dados recebidos por cada usuário. É muito pesado. E não por acaso essa discussão começou a surgir no fim dos anos 2000. Justamente quando redes sociais como Facebook, Twitter, estavam atingindo proporções que nenhuma outra plataforma nunca tinha atingido até aquele ponto. Sem contar o advento de Mobile e da popularização de games online.</p>
<p>Nessas circunstâncias, de fato, o esquema de bancos de dados relacionais atinge seus limites. As pesquisas ficam lentas pelo volume absurdo de dados. Pior, as escritas ficam absurdamente lentas, em parte por causa das garantias que estávamos acostumados a ter, como durabilidade. A idéia de que se mandamos o servidor gravar uma linha e ele diz &ldquo;ok&rdquo; significa que aquela linha garantidamente está no disco. Nesses cenários de Big Data, essa garantia se torna um problema.</p>
<p>Quando falamos em Big Data, precisamos lidar com premissas diferentes. Num banco, se sumir uma linha do seu extrato, é um grande problema. Literalmente a conta não vai fechar. Vai faltar um depósito. Vai faltar um pagamento. Dinheiro vai sumir. Pior, se uma linha for duplicada. Mesma coisa num ecommerce. Mesma coisa num sistema com da Receita Federal. Todos precisam de garantias na consistência e integridade dos dados. Por isso existe e sempre vai existir um lugar pra bancos de dados com características que um Oracle, SQL Server ou Postgres oferecem hoje.</p>
<p>Mas pra certos tipos de dados, como analytics, como dados de batimentos cardíacos, como dados de jogos online, mesmo se você perder ou duplicar uma linha dessas, qual o impacto? É baixo ou, no caso geral, nenhum impacto. Estando dentro de uma margem de erro, no fim do dia, você não tá interessado em cada clique individual no seu site, estamos interessados na quantidade geral por dia, por semana, por mês. Não estamos interessados no batimento exato de 5 minutos atrás, mas sim na média por hora, na média durante um exercício ou na média durante o sono. E assim por diante.</p>
<p>É por isso que a partir do começo dos anos 2010 começaram a surgir bancos de dados não relacionais, os NoSQL. Bancos de dados criados pra responder situações diferentes, em particular de Big Data, onde queremos lidar com séries de dados consolidados. Onde velocidade de escrita e baixa latência tem prioridade sobre consistência. Queremos poder escrever em múltiplos servidores ao mesmo tempo, mesmo que durante algum período de tempo, os dados fiquem ligeiramente inconsistentes.</p>
<p>O problema é como distribuir a carga, tanto de processamento pra pesquisa, quanto pra escrita, e como dividir os dados entre múltiplos servidores diferentes, pra permitir escalabilidade? Recapitulando: bancos de dados relacionais funcionam muito bem num único servidor, oferecendo as garantias ACID de atomicidade, consistência, isolamento e durabilidade que todos nós gostamos. Tem opções pra escalar via réplicas somente de leitura e, até certo ponto, replicação bi-direcional, que é sempre complicado. Mas pra situações de Big Data e volumes gigantes de escritas, começa a se tornar uma tarefa complicada, chata e com poucas opções.</p>
<p>Não tem como falar de NoSQL sem mencionar o famoso teorema CAP, proposto por Eric Brewer no ano 2000. Foi um divisor de águas no estudo de sistemas distribuídos e provavelmente você vai aprender no curso de ciências da computação, mas em resumo ele propõe que bancos de dados verdadeiramente distribuídos, ou seja, que rodam em múltiplos servidores, ou múltiplos nós de um cluster, no máximo satisfazem duas de três propriedades: consistência, availability ou disponibilidade e partition tolerance ou tolerância a partição.</p>
<p>Ou temos consistência e durabilidade, mas o sistema não tolera partições, ou seja, alguns nós do cluster podem ficar offline do nada. Isso não é legal porque não é impossível um servidor dar problema, ou mesmo termos que tirar um deles do ar pra manutenção. Então queremos consistência e tolerância a partição, mas aí temos baixa disponibilidade, ou vamos ter o ideal, disponibilidade e tolerância a partição, mas teremos baixa consistência, ou o que chamamos de &ldquo;consistência eventual&rdquo;, onde um servidor, por um ligeiro período de tempo, pode não ter os mesmos dados que os outros servidores.</p>
<p>CAP é um teorema e não uma teoria, ou seja, não é uma lei. Quando estudar vai encontrar que nem todo mundo concorda com todos os argumentos, em particular, porque os termos consistência, disponibilidade e tolerância a partição podem ser facilmente mal interpretados. Não tem uma definição muito exata pra cada coisa, e todo mundo sabe o que acontece quando termos ficam abertos à interpretação: ninguém se entende. E isso acontece com frequência, porque na minha cabeça eu quero falar de consistência mas você tá entendendo como integridade. E uma coisa é diferente da outra.</p>
<p>Daí tem o problema da natureza binária. É um dos poucos lugares que de fato não podemos levar ao pé da letra que só podemos escolher 2 propriedades das 3. No caso de termos disponibilidade e tolerância, não significa que não vamos ter nenhuma consistência. Depende se precisamos de consistência forte, onde é obrigatório que todas as réplicas sempre tenham exatamente o mesmo dado correto, ou se podemos ter consistência fraca, eventual, onde durante alguns segundos, os dados diferem entre os servidores. De novo, num banco, não podemos ter um saldo, recarregar o navegador e aparecer outro saldo, mas no exemplo dos batimentos cardíacos, se faltar um batimento durante 1 minuto, não vai ser um problema, não quer dizer que você teve uma parada cardíaca nem nada.</p>
<p>A outra coisa é tolerância a partição. De novo, pense num data center, onde um grande serviço pode ter um banco distribuído em múltiplos data centers, uma dúzia de servidores em Nova Iorque, outra dúzia de servidores em Londres, todos sincronizados, mas aí temos uma pane na internet entreeles. Um link submarino deu pane, caiu a fibra ótica. É raro acontecer, mas acontece. E o sistema tem que saber o que fazer quando acontecer, ou seja, ser tolerante a essa partição da rede.</p>
<p>O problema é que isso realmente é muito raro de acontecer. Nossa infraestrutura dos anos 2020 é mil vezes melhor que nos anos 2000, hardware, redes, infra em geral é muito mais estável. Então nossos sistemas não precisam ser tão agressivos na preparação pra uma situação tão rara. Talvez seja melhor ser otimizado considerando o caso que não vai acontecer, e colocar estratégias de degradação graciosa caso aconteça, como simplesmente uma página de &ldquo;aguarde alguns segundos&rdquo;. Mesmo se tiver pane na rede, hoje em dia, se recupera em poucos minutos. Antigamente poderia levar horas ou dias.</p>
<p>Essas são algumas das considerações ao se discutir o teorema CAP. Na prática vamos lidar com alguma estratégia de sharding e alguma estratégia de replicação de dados. Replicação é como acontece com um banco de dados relacional, onde podemos adicionar nós na rede, e eles vão receber uma cópia dos dados, daí cada servidor vai ficar idêntico ao outro, fornecendo redundância e possibilitando dividir a carga dos usuários pra mais servidores.</p>
<p>No caso de banco de dados relacional, por causa da característica de consistência forte, ficamos limitados na quantidade de servidores que podemos ter pra escrita. Num NoSQL, com consistência eventual, qualquer dos nós pode ser usado pra escrita, mas vai levar um tempo pra replicar os dados pros outros servidores. A consistência vai existir, mas vai ser eventual, ou seja, eventualmente vai estar tudo consistente.</p>
<p>Na real, bancos de dados NoSQL como Cassandra ou Dynamo tem como configurar que nível de consistência queremos ter. Mais forte ou mais fraco. Podemos configurar isso dependendo das necessidades. Uma coisa é um pequeno cluster de 3 servidores de Cassandra tudo no mesmo data center, na mesma rede. Outra coisa são múltiplos clusters de dúzias de servidores em vários de data center diferentes, como é a operação do tamanho de uma Netflix.</p>
<p>Bancos como Cassandra ou Dynamo foram feitos mais pra situações onde precisamos funcionar em múltiplos data centers. Dividido em regiões geográficas diferentes. Um no Japão, outro na Europa, outra na América e todos os servidores de todos os clusters sincronizando entre si. Não é uma situação que vamos encontrar todo dia.</p>
<p>Pra maioria das empresas médias, em vez de lidar com uma infraestrutura dessas do zero, o mais prático seria usar um DynamoDB da AWS. Como tudo da AWS, eles gerenciam a maior parte dessa complexidade e podemos só usar. Claro que, por causa disso, o custo por transação é bem mais caro. Mas é esse o trade-off que precisa ser estudado. Ou vamos ter que investir em contratar pessoal especializado em infraestrutura, pra ficar 24 por 7 lidando com hardware, bare metal, num data center.</p>
<p>Ambos Cassandra e Dynamo oferecem capacidades de particionamento e replicação. MongoDB também tem isso, mas ele chama de sharding e replica sets. O que buscamos é a capacidade de conseguir particionar os dados em múltiplos servidores de forma balanceada, e de ter replicação entre alguns desses servidores pra ter redundância. Se um deles tiver uma pane e cair, tem outra cópia idêntica pra continuar servindo os mesmos dados. É muito complexo pra tentar detalhar em um video, mas eu queria que vocês pelo menos soubessem que isso existe pra saber onde procurar depois.</p>
<p>Pra agora é interessante conhecer um último conceito: IDs. Voltando pro exemplo lá do começo, a tabela de contatos feito em DBF com dBase. Lembram que falei que mesmo se eu fizer um índice de nome, precisaria procurar pelo nome completo pra achar? Hoje em dia, em qualquer banco de dados, todos oferecem alguma forma de pesquisar parcialmente ou palavras que soam parecido. É o que um Elasticsearch, Solr ou os recursos de full-text search de um Postgres e outros bancos de dados oferecem. É um índice diferente pra esse tipo de pesquisa em particular. Expliquei um isso no episódio do Twitter, vejam lá depois.</p>
<p>Antigamente, nos anos 80, não tínhamos o poder de processamento pra fazer coisas desse tipo. Em vez de tentar lembrar se &ldquo;Brian Russell&rdquo; tem dois &ldquo;S&rdquo; ou dois &ldquo;L&rdquo;, em muitos casos era mais fácil lembrar o código dele, o ID. Por isso toda tabela a gente se acostumou a colocar esse código. Assim como todo pedido de compra tem um código. Assim como todo indivíduo nesse país é um CPF. Toda linha numa tabela é um código numérico, que usamos como uma chave primária ou, no caso de bancos relacionais, usamos pra fazer uma linha em uma tabela referenciar uma linha em outra tabela, e chamamos esses IDs de chaves estrangeiras, porque ela é estrangeira à tabela.</p>
<p>O jeito considerado errado hoje, principalmente em códigos que vamos divulgar pros usuários, é fazer a chave primária ser um número auto-incremental. Ou seja, começando de 1 e incrementando, próxima linha sendo 2, linha seguinte sendo 3 e assim por diante. Um erro de segurança que foi muito comum antigamente era ter uma aplicação web onde a URL mostra esse código, por exemplo, digamos que tivesse <code>mercadolivre.com/users/1</code> que é a URL pra página com os dados cadastrais de um usuário.</p>
<p>Num sistema bem feito, quando o usuário faz login, eu guardo o ID dele na sessão. Quando ele navega pra outras páginas privadas, checamos com o ID na sessão antes de mostrar a informação. Se ele tentar acessar a URL que seria de outro usuário, conseguimos invalidar o pedido e devolver um erro 403 não permitido. Esse seria o certo, mas num sistema grande, alguém pode deixar passar um bug e não checar.</p>
<p>Daí alguém de fora, percebendo que estão usando IDs auto incrementais, e o dele é ID 1000, óbvio que significa que tem usuários abaixo de 1000 e alguns acima, e ele vai começar a tentar forçar IDs até alguma página abrir na conta de outro usuário. O certo é não ter esse bug. Mas também não é legal oferecer informação de graça pra estranhos, como a existência de outros usuários com IDs fáceis de achar.</p>
<p>O ideal é usar um número aleatório gigante. Um campo de inteiro de 32 bits pode conter números de 0 até mais de 4 bilhões. Mas se for um inteiro de 64 bits, estamos na ordem de 18 milhões de trilhões. É muito número. Por isso não precisamos usar números sequenciais auto incrementais, use números aleatórios. Vai ser perto de impossível tentar adivinhar o ID de alguém.</p>
<p>Pra isso usamos funções geradores de números como UUIDs. Nem precisamos usar o número inteiro que ele gera, basta truncar e pegar um pedaço e as chances de colisão, ou seja, de escolher um número que já existe na sua tabela, vai ser hiper baixo. E como o certo é criar campos de chave primária com constraint de unicidade, ele não vai deixar inserir um ID duplicado. Existem dezenas de estratégias pra gerar números aleatórios pra IDs, vale a pena parar pra pesquisar e evitar números sequenciais. Chamamos isso de chaves surrogadas. Não é nenhuma lei, mas é uma boa prática. Pra tabelas internas, cujos IDs você nunca vai mostrar em público, aí tanto faz.</p>
<p>Expliquei isso porque em sistemas distribuídos, temos o problema de balancear dados entre diferentes servidores. Quando replicamos dados, estamos fazendo uma cópia dos dados de um servidor pra outro, pra servir de redundância, mas num sistema grande distribuído, queremos dividir o processamento, e isso exige que alguns servidores vão ter dados que outros servidores não vão ter. Deixa eu dar um exemplo besta.</p>
<p>Digamos que trabalho na Receita Federal. Eca. Fazer um único banco de dados com todos os impostos de renda, de todas as pessoas e empresas, num único banco de dados, é bem pesado. Ainda mais se tiver a informação histórica de todo mundo das últimas décadas. Um jeito ingênuo de lidar com isso é manualmente dividir os dados. Por exemplo, posso fazer um servidor com dados da região Sudeste, outro servidor só pra região Sul, outro só pra região Nordeste e assim por diante.</p>
<p>Problema que um servidor vai continuar tendo mais volume de dados que outros. Não vai ser balanceado. Rapidamente o da região Sudeste vai precisar dividir. Daí teríamos que fazer um servidor só pra São Paulo separado. Mas mesmo assim, o volume só de São Paulo sempre vai ser muito maior que tudo, vamos dividir pelo menos a cidade de São Paulo num único servidor sozinho. Tá vendo o trampo? Ficar tentando dividir os dados assim manualmente vai ser um parto. Pior ainda pro software. Teríamos que fazer algo como &ldquo;if sao paulo, procura no servidor X&rdquo;, ou &ldquo;if regiao nordeste, procura no servidor Y&rdquo;. Isso rapidamente fica caótico.</p>
<p>Existe uma solução melhor. Antes, vamos ver um outro jeito ingênuo de balanceamento. Alguém poderia pensar &ldquo;hum, e se não olharmos pros dados, esquece isso de região ou estado, vamos olhar só pro ID&rdquo;. Nesse caso digamos que os IDs são numéricos e sequenciais, exatamente como eu disse pra não fazer. Daí temos 3 servidores que vamos chamar de A, B e C e 12 itens na tabela, com ID de 1 a 12, pra simplificar. Como eu divido esses 12 itens entre os 3 servidores?</p>
<p>Fácil, que tal usarmos o módulo do ID? Lembra a idéia de módulo? No caso faríamos módulo 3. Módulo é quando você pega o restante da divisão de um número por outro. Por exemplo. 1 módulo de 3 seria 1, 2 módulo de 3 seria 2, 3 módulo de 3 é 0, por que 3 dividido por 3 dá um e sobra zero. Interessante é acima disso. 4 dividido por 3 é 1 e sobra 1, então o módulo é 1.</p>
<p>5 dividido por 3 é 1 e sobra 2, então o módulo é 2, mas 6 dividido por 3 é exato 2 e sobra zero, o módulo é zero. Qualquer número módulo 3 vai ser um número de 0 a 2. Sempre vai rotacionando. Estabelecendo arbitrariamente que o servidor A é zero, B é 1 e C é 2, sabemos como dividir cada uma das 12 linhas em cada um dos 3 servidores, sacaram?</p>
<p>Então no servidor A teremos os itens com IDs, 1, 4, 7 e 10. No servidor B teremos os itens com IDs 2, 5, 8 e 11 e no servidor C teremos os itens com IDs 3, 6, 9 e 12, todos os múltiplos de 3. E toda vez que quisermos inserir uma nova linha, fazemos o módulo do ID e vemos pra qual servidor mandar. Isso garante que todos  vão estar balanceados, ou seja, todos vão ter exatamente o mesmo número de linhas. Resolvido então?</p>
<p>Mas é a Receita Federal, eles crescem infinitamente. Muito em breve só 3 servidores não vai dar conta. Precisamos adicionar mais um novo servidor. Agora fodeu. A conta passa de módulo 3 pra módulo 4. Ou seja, precisamos rebalancear todos os dados em todos os servidores. Na nova configuração, cada ID vai ficar num servidor diferente do que tava antes. Essa estratégia funciona pra balancear se nunca mexermos na quantidade de servidores, mas  não escala se precisarmos adicionar novos, porque precisa mover todos os dados de lugar pra rebalancear.</p>
<p>Por isso foi inventado o algoritmo de Consistent Hashing em computação distribuída. É um esquema de organização de IDs que permite adicionar ou remover servidores sem precisar mover todos os dados de lugar toda vez. Ele consegue isso organizando todos os IDs num anel, um círculo de hashes. Pra entender hashes recomendo que assistam o primeiro episódio de criptografia.</p>
<p>Em resumo bem resumido, hash é uma função que pega qualquer informação, por exemplo uma string, e devolve um número de tamanho fixo. Mas é uma função não reversível, ou seja, a partir do número não tem como encontrar a string original, porque diversas strings podem chegar no mesmo número. A função só garante que se você passar a mesma string, vai chegar no mesmo número.</p>
<p>Dependendo da função ela devolve um número até um determinado tamanho, por exemplo, um SHA512, vai devolver um número de até 512 bits. O número máximo de 512 bits é um absurdo maior que a quantidade de átomos no universo visível. Obviamente é exagerado demais pra um campo de ID. Um Cassandra usa outra função de hash não criptográfico, ou seja, que não deve ser usado pra criptografia, mas é bom o suficiente pra IDs, o Murmur3 que devolve hashes de 64 bits, onde o maior número é da ordem de milhões de trilhões, que ainda é um número gigante, mas mais gerenciável.</p>
<p>Enfim, o principal é que no sistema de consistent hashing consideramos um anel que vai de 0 até esse número máximo de 64 bits. Daí fazemos o hash dos IDs dos servidores. Digamos que temos 4 servidores. Eles se distribuem pelo anel. O lance de uma função de hash é que ela tende a ter uma distribuição uniforme, o oposto de sequencial. Podemos gerar o hash dos dados do registro, com a combinação de diversos dados como o endereço IP do usuário mais o horário, ou qualquer coisa assim.</p>
<p>Por exemplo, imagine um anel onde cada ponto desse anel começa em 0 no topo e tem todos os números até 2 elevado a 64, no final do anel, encostado no zero, pra dar uma volta. Entenderam, todos os IDs possíveis estão no anel. Daí aplicamos a função de hash pros nomes ou endereços IP de cada servidor, e aí eles caem arbitrariamente pelo anel, só pro exemplo, o servidor A lá em cima, depois o B mais pra direita lá embaixo e o C mais pra esquerda embaixo.</p>
<p>Vamos inserir alguns dados, digamos que é o hash do nome do registro. O primeiro registro tem o nome de John e digamos que o hash gerado caia entre o Nó A e o Nó B. A regra é pra ele escolher o servidor com o hash maior ou igual ao do ID olhando na direção de relógio, da esquerda pra direita. Nesse caso vai ser o Nó B. O próximo registro tem nome de Fred e o hash por acaso tem ponto aqui entre o B e o C, então ele cai no Nó C. E o último registro com nome de Susan cai aqui entre o C e o A, então vai no A. Sempre na direção de relógio.</p>
<p>Beleza e qual a vantagem disso? Bom, vamos voltar pro cenário onde queremos adicionar um novo servidor, o nó D e o hash dele vai aqui entre o C e o A, por acaso. Mas ele cai depois da Susan. O que acontece é que todos os dados com IDs depois do C que estavam caindo no Nó A, agora precisam ser movidos pro Nó D. É assim que rebalanceia. Mas prestem atenção, no exemplo de usar a função simples de módulo, precisaria mover todos os dados de todos os servidores quase. Aqui, os dados dos servidores B e C não precisam ser mexidos, só os do A que precisam ser divididos. Os registros com IDs maiores que D e menores que A, continuam no A.</p>
<p>Digamos que o servidor A tenha uma pane qualquer e precise ser removido. E agora? Todo registro que antes cairia no A, pela regra de olhar o próximo servidor em sentido de relógio no anel, então vai passar a cair no B. Só isso. E nenhum dado de nenhum outro servidor precisa ser mexido. Falando simplificadamente é assim que garantimos ter a menor quantidade possível de dados sendo movidos quando a infraestrutura muda. A idéia é mover a menor quantidade de dados quanto possível quando precisamos adicionar novos servidores.</p>
<p>Tem várias nuances nessa estratégia. Por exemplo, quando da remoção de um Nó, pode acontecer de outro Nó acabar acumulando dados demais. Pra evitar isso, cada Nó pode ter múltiplos hashes aleatórios que o colocariam em múltiplas posições no anel, daí os dados poderiam ser melhor rebalanceados. Se ficou interessado, depois leia a documentação a respeito em sites como do Cassandra ou no paper do DynamoDB da Amazon.</p>
<p>Dizendo assim parece fácil, mas em DynamoDB por exemplo, você tem que entender o conceito de chave de partição e chave opcional de ordenamento. Dados com a mesma chave de partição caem na mesma partição, obviamente. O problema é se quisermos fazer uma pesquisa que precisaria buscar dados em múltiplas partições. Pra isso tem comandos com o &ldquo;Scan&rdquo;, mas isso é uma operação bem mais custosa do que pesquisar dentro de uma mesma partição.</p>
<p>Portanto a estratégia de partição precisa estar muito bem definida no começo, senão vai ter obstáculos de não conseguir fazer pesquisas rápidas quando precisar. As coisas não são automáticas, trocamos conveniência por performance. Num banco relacional, onde normalmente fica tudo junto, é só rodar um comando SQL e temos o que queremos rápido. Num sistema distribuído as coisas estão, duh, distribuídas, e precisamos levar isso em consideração.</p>
<p>No DynamoDB, que é oferecido com um serviço da AWS, precisamos entender bem o comportamento da nossa aplicação, não só pra não dificultar operações depois, mas porque a cobrança também não é simples. Não se paga por gigabyte armazenado, por exemplo. Precisa calcular a quantidade de escritas por segundo. A quantidade de escritas por segundo durante um pico. A duração de cada operação de leitura, porcentagem de capacidade de leitura que quer deixar reservado, o nível de consistência das leituras, quantas quer que sejam fortemente consistemente, quantas fracamente consistentes.</p>
<p>Por exemplo, na calculadora online deles, o preço por gigabyte, na região US east, é de meros 25 centavos por mês. Sucesso então? Não, com 10 escritas não transacionais, 100 escritas por segundo em horário fora de pico, 400 escritas por segundo em horário de pico, 72 horas operando em pico durante o mês, com capacidade reservada de escrita pra um ano, pode somar mais 23 dólares por mês e 150 dólares que precisa pagar a vista logo no começo pra reserva.</p>
<p>Daí continuando com os valores padrão pra leitura, só pra não entediar, mais 3 dólares por mês e mais 30 dólares upfront à vista. Então, por mês nessa configuração vai pagar uns 26 dólares, mais 180 dólares à vista na entrada. E essa configuração é a mais basiquinha, pra um app pequeno.</p>
<p>Na categoria de bancos de dados NoSQL oferecidos como serviço, ou seja, que não precisamos saber como configurar e dar manutenção na infraestrutura, tem diversas outras opções, como MongoDB Atlas ou Google Firebase. Firebase é bem popular pra aplicações pequenas, porque até 1 gigabyte de mensagens, até 1 gigabyte de armazenamento, 5 gigabytes de cloud storage, o custo é gratuito. Já um MongoDB Atlas você paga até que &ldquo;barato&rdquo; a 10 centavos por milhão de operações de leitura, ou o recomendado 57 dólares por mês pra solução dedicada de 10 giga até 4 terabytes de armazenamento.</p>
<p>Não é simples calcular o real valor que vai pagar, tem dezenas de parâmetros pra considerar, como quantidade de invocação de funções e gigabytes por segundo ou minutos de cloud build ou gigabytes transferidos pela rede e mais. O preço vai variar de acordo com o sucesso ou fracasso da sua aplicação. Quanto mais &ldquo;barato&rdquo; ficar, mais fracassado é seu app, quanto mais sucesso fizer, mais caro vai ficar. No começo parece uma grande vantagem, mas justo quando fizer sucesso e achar que vai ganhar bastante, é também quando eles vão te cobrar mais caro. Essa é a faca de dois gumes de serviços que você não gerencia. Uma hora a conta chega.</p>
<p>Isso sem contar o custo do desenvolvimento e manutenção da sua aplicação. Quanto mais exótico for um banco de dados, mais difícil vai ser encontrar profissionais qualificados ou treinar em casa. Só porque um banco é exótico, não significa que resolve seus problemas. Em muitos casos, alguém pode escolher o Google Firebase pelo hype mas na realidade o desenvolvimento teria ficado mais barato se tivesse escolhido, digamos um MongoDB Atlas.</p>
<p>Ambos Firebase e MongoDB tem a flexibilidade de lidar com documentos estilo JSON, sem precisar se prender à estrutura mais rígida de um schema de banco de dados relacional. Mas só dizer JSON não significa que são iguais. MongoDB permite lidar com documentos mais complexos, embedded arrays, sub-documentos. O modelo do Firebase é bem mais simples.</p>
<p>Mas o Firebase tem atualizações em tempo real, que é importante pra quem desenvolve aplicações mobile. Mudanças no banco podem ser empurradas como notificações direto pros usuários em tempo real. MongoDB não tem isso nativo. Dá pra colocar mas dá mais trabalho, portanto é mais custo de desenvolvimento.</p>
<p>Por outro lado, se quisermos fazer pesquisas mais complexas, MongoDB tem uma linguagem de queries mais avançada, incluindo coisas como joins e índices secundários. Firebase tem capacidades de query, mas limitadas. Da mesma forma, transações. Todo mundo foge de banco relacional, mas todos adoram ter transações e dados fortemente consistentes. MongoDB tem mais suporte a transações do que Firebase. De novo, se quiser algo semelhante a transações no Firebase, o custo de desenvolvimento sobe. E se transações é realmente importante, talvez um banco relacional como AWS RDS tivesse sido melhor, em primeiro lugar.</p>
<p>Mas a vantagem de serem &ldquo;as a service&rdquo; quer dizer que tanto MongoDB Atlas quanto o Filestore do Firebase tem escalabilidade e replicação automáticas em múltiplas regiões pra alta disponibilidade. Ou seja, se throughput de operações e garantir que nunca fique fora do ar seja primordial, ambos podem ajudar com isso. Mas você vai pagar por isso, e não vai ser barato. Como expliquei, o modelo de precificação é complexo. Precisa seriamente gastar tempo pesquisando e avaliando muito bem o que acontece não só agora, que sua aplicação tá no começo, mas daqui seis meses se fizer sucesso.</p>
<p>Não tem como eu explicar todos os bancos de dados oferecidos como serviços hoje. A Amazon é a mais famosa. Todo mundo escolhe o AWS RDS pra soluções de MySQL ou Postgres como serviço, mas o Google também tem o Cloud SQL como concorrente. Vai depende de precificação e funcionalidades que pra você podem ser importantes. Além deles tem o Azure SQL Database e acho que até a Oracle oferece o banco de dados SQL deles como serviço.</p>
<p>Pra complicar a vida, a própria Amazon oferece produtos similares na mesma plataforma, como o Apache Aurora que parece com RDS. Aurora é um produto superior de banco de dados MariaDB, Postgres, SQL Server, Oracle, tem maior garantia de disponibilidade, dobro da performance no mesmo perfil de hardware, mais facilidade de escalabilidade, opções de backup, replicação, porém, obviamente custa mais caro. Ou seja, tem SQL pra todos as necessidades e capacidade de pagamento.</p>
<p>Digamos que você é um apologista de NoSQL, não quer mesmo SQL. Daí tem o MongoDB Atlas e Google Firestore que já falei, mas temos mais opções. Pro mundo enterprise, a SAP oferece o HEC que é o HANA Enterprise Cloud. Tem a ScyllaDB que oferece uma versão de Cassandra deles e tem o AstraDB da DataStax que também é Cassandra como serviço. Isso sem contar o Heroku que oferece Postgres como serviço, Redis como serviço. E em termos de cache, Memcache e Redis, a AWS tem o Elasticache como já falei. Tem serviços pra todo mundo, é só conseguir pagar.</p>
<p>Mas, pra você que é iniciante em programação, não se deixe enganar por hype de NoSQL. A gente vem discutindo esse assunto desde pelo menos 2008. E eu posso te garantir algumas coisas: não existe consenso, pelo simples fato que cada banco de dados tem pontos fortes e pontos fracos, cada um serve pra situações diferentes. Pra aplicações bem pequenas, meio que tanto faz se vai escolher um banco relacional SQL como um MariaDB ou Postgres ou se vai direto pra um FireStore da vida. Você tem poucos dados, tem poucos problemas pra lidar.</p>
<p>A primeira versão de qualquer coisa que fizer sempre vai ser isso: versão 1.0. Só quando tiver usuários de verdade, dados de verdade, receita e faturamento de verdade, só aí vai realmente entender o que precisa. E se ficar limitado a usar só uma ferramenta pra tudo, em breve vai ter tanto problema, e vai ignorar que existem já soluções pra cada problema, que corre o risco de fracassar por ignorância.</p>
<p>Na dúvida, a solução mais comum é a mesma: AWS RDS com Postgres ou MariaDB, que é o que a grande maioria dos frameworks web usam, como Ruby on Rails. Django, Laravel, Spring. Daí uma solução de cache usando Redis ou Memcache, e pra isso tem AWS ElastiCache. E finalmente jobs assíncronos, seja com Kafka, seja com AWS SQS. Isso resolve 90% dos seus problemas. Se precisar de alguma coisa de documentos schemaless em JSON, um Postgres já suporta isso nativamente. Quer só fazer um protótipo, vai de Firebase.</p>
<p>Se por acaso estiver na rara categoria de Big Data, significa que você não é uma micro empresa, ou um freelancer. Já é uma empresa média. Aí é outra categoria, já vai olhar pra Aurora, DataStax, e soluções pra Data Science como Apache Spark, Hadoop e muita coisa customizada. Vai ter profissionais experientes de infraestrutura de devops. Essa categoria não é pra amadores. Por isso, se for iniciante, não se preocupe com isso tão cedo. Você não é o Netflix.</p>
<p>No fim do dia, entender bancos de dados, sejam relacionais, sejam NoSQL, significa entender o problema que se quer resolver primeiro. Daí aplicar fundamentos de ciências da computação. Árvores, estruturas de dados, redes, sistemas de arquivos, sistemas operacionais, fundamentos de criptografia. Ficar acompanhando discussões inúteis em fios de Twitter da bolha dev, só vai te confundir, nunca tem nada de útil e você vai ser mais um maria-vai-com-as-outras. Se atenha a pesquisar documentação oficial de cada coisa.</p>
<p>A grande maioria dos programadores iniciantes hoje tem o mesmo problema de ignorar fundamentos, ignorar os problemas que se quer resolver, e escolher baseado em likes. Influencer nenhum sabe o seu cenário particular. Óbvio que vai dar ruim. Se ficaram com dúvidas mandem nos comentários abaixo, se curtiram o video deixem um joinha, assinem o canal, e não deixem de compartilhar o video com seus amigos. A gente se vê, até mais.</p>
]]></content:encoded><category>bancos-de-dados</category><category>sistemas-distribuidos</category><category>cloud</category><category>akitando</category></item><item><title>[Akitando] #135 - ChatGPT Consegue te Substituir? | Entendendo Jobs Assíncronos</title><link>https://akitaonrails.github.io/2023/01/05/akitando-135-chatgpt-consegue-te-substituir-entendendo-jobs-assincronos/</link><guid isPermaLink="true">https://akitaonrails.github.io/2023/01/05/akitando-135-chatgpt-consegue-te-substituir-entendendo-jobs-assincronos/</guid><pubDate>Thu, 05 Jan 2023 11:00:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/Yl-hlwhj2B0"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;Vamos entender mais sobre Jobs Assíncronos, a diferença de usar Promises ou Bull. E um pouco mais sobre o comportamento do famigerado ChatGPT. Vamos ver se ele realmente substitui programadores durante os códigos deste video.&lt;/p&gt;
&lt;h2&gt;Capítulos&lt;span class="hx:absolute hx:-mt-20" id="capítulos"&gt;&lt;/span&gt;
&lt;a href="#cap%c3%adtulos" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;00:00 - Intro&lt;/li&gt;
&lt;li&gt;01:42 - Cap 1 - Form de Upload com ChatGPT | Coisas que você não pensa&lt;/li&gt;
&lt;li&gt;04:55 - Cap 2 - Upload Direto pra Cloud | Porque não gravar local&lt;/li&gt;
&lt;li&gt;09:23 - Cap 3 - Ajustando o Front | ChatGPT é júnior?&lt;/li&gt;
&lt;li&gt;11:46 - Cap 4 - Jogando o Back todo dentro do Controller | Como avaliar Back pesado&lt;/li&gt;
&lt;li&gt;18:26 - Cap 5 - Promises, não: Jobs Async | Controlando seu back&lt;/li&gt;
&lt;li&gt;23:52 - Cap 6 - Exemplos Reais de Jobs Async | Google, Anchor&lt;/li&gt;
&lt;li&gt;26:00 - Cap 7 - Precisa de Jobs Async se Java tem Threads? | Apache Kafka&lt;/li&gt;
&lt;li&gt;29:51 - Cap 8 - Qual o nível do ChatGPT | Errando com Convicção&lt;/li&gt;
&lt;li&gt;33:27 - Cap 9 - Quanto custa um ChatGPT? | IA é inacessível&lt;/li&gt;
&lt;li&gt;36:40 - Cap 10 - ChatGPT vai te substituir? | Talvez?&lt;/li&gt;
&lt;li&gt;38:50 - Bloopers&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Links&lt;span class="hx:absolute hx:-mt-20" id="links"&gt;&lt;/span&gt;
&lt;a href="#links" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.bullmq.io/guide/connections"target="_blank" rel="noopener"&gt;https://docs.bullmq.io/guide/connections&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/bull-ui"target="_blank" rel="noopener"&gt;https://www.npmjs.com/package/bull-ui&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ShaneK/Matador"target="_blank" rel="noopener"&gt;https://github.com/ShaneK/Matador&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.baeldung.com/spring-kafka"target="_blank" rel="noopener"&gt;https://www.baeldung.com/spring-kafka&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://youtu.be/yE-XLLa2AME?t=642"target="_blank" rel="noopener"&gt;https://youtu.be/yE-XLLa2AME?t=642&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://youtu.be/0uQqMxXoNVs?t=551"target="_blank" rel="noopener"&gt;https://youtu.be/0uQqMxXoNVs?t=551&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jdhao.github.io/2022/08/10/http_benchmark_wrk/"target="_blank" rel="noopener"&gt;https://jdhao.github.io/2022/08/10/http_benchmark_wrk/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;SCRIPT&lt;span class="hx:absolute hx:-mt-20" id="script"&gt;&lt;/span&gt;
&lt;a href="#script" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Olá pessoal, Fabio Akita&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/Yl-hlwhj2B0"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>Vamos entender mais sobre Jobs Assíncronos, a diferença de usar Promises ou Bull. E um pouco mais sobre o comportamento do famigerado ChatGPT. Vamos ver se ele realmente substitui programadores durante os códigos deste video.</p>
<h2>Capítulos<span class="hx:absolute hx:-mt-20" id="capítulos"></span>
    <a href="#cap%c3%adtulos" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li>00:00 - Intro</li>
<li>01:42 - Cap 1 - Form de Upload com ChatGPT | Coisas que você não pensa</li>
<li>04:55 - Cap 2 - Upload Direto pra Cloud | Porque não gravar local</li>
<li>09:23 - Cap 3 - Ajustando o Front | ChatGPT é júnior?</li>
<li>11:46 - Cap 4 - Jogando o Back todo dentro do Controller | Como avaliar Back pesado</li>
<li>18:26 - Cap 5 - Promises, não: Jobs Async | Controlando seu back</li>
<li>23:52 - Cap 6 - Exemplos Reais de Jobs Async | Google, Anchor</li>
<li>26:00 - Cap 7 - Precisa de Jobs Async se Java tem Threads? | Apache Kafka</li>
<li>29:51 - Cap 8 - Qual o nível do ChatGPT | Errando com Convicção</li>
<li>33:27 - Cap 9 - Quanto custa um ChatGPT? | IA é inacessível</li>
<li>36:40 - Cap 10 - ChatGPT vai te substituir? | Talvez?</li>
<li>38:50 - Bloopers</li>
</ul>
<h2>Links<span class="hx:absolute hx:-mt-20" id="links"></span>
    <a href="#links" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li><a href="https://docs.bullmq.io/guide/connections"target="_blank" rel="noopener">https://docs.bullmq.io/guide/connections</a></li>
<li><a href="https://www.npmjs.com/package/bull-ui"target="_blank" rel="noopener">https://www.npmjs.com/package/bull-ui</a></li>
<li><a href="https://github.com/ShaneK/Matador"target="_blank" rel="noopener">https://github.com/ShaneK/Matador</a></li>
<li><a href="https://www.baeldung.com/spring-kafka"target="_blank" rel="noopener">https://www.baeldung.com/spring-kafka</a></li>
<li><a href="https://youtu.be/yE-XLLa2AME?t=642"target="_blank" rel="noopener">https://youtu.be/yE-XLLa2AME?t=642</a></li>
<li><a href="https://youtu.be/0uQqMxXoNVs?t=551"target="_blank" rel="noopener">https://youtu.be/0uQqMxXoNVs?t=551</a></li>
<li><a href="https://jdhao.github.io/2022/08/10/http_benchmark_wrk/"target="_blank" rel="noopener">https://jdhao.github.io/2022/08/10/http_benchmark_wrk/</a></li>
</ul>
<h2>SCRIPT<span class="hx:absolute hx:-mt-20" id="script"></span>
    <a href="#script" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Olá pessoal, Fabio Akita</p>
<p>Todo mundo parece que gostou do episódio de técnicas de otimização web, onde explico porque o aplicativo que você acabou de conseguir terminar seguindo um tutorial ou curso online não se parece nada com um projeto profissional de verdade. Não foi surpreendente como realmente, a maioria dos iniciantes nem imaginava que aquelas técnicas existiam. E quem já tem experiência também confirmou que tudo que falei é o arroz com feijão.</p>
<p>Na mesma semana, em dezembro de 2022, tava todo mundo super hypado com o ChatGPT. Acho que todos assistindo pelo menos ouviram falar dessa inteligência artificial, da OpenAI do Elon Musk, que consegue fazer sua lição de casa, até escrever uma matéria jornalística ou script pra videos de YouTube sem nenhum esforço e, pra nós programadores, o relevante é ele conseguir escrever código direitinho. Muitos YouTubers até prevêem o fim dos programadores. Será? Se você pensou assim também, hoje vou explicar porque está errado!</p>
<p>(&hellip; )</p>
<p>Recapitulando, no episódio de otimização expliquei rapidamente o conceito básico de balanceador de carga, tanto no nível de requisições HTTP com um NGINX ou entre sua aplicação e o banco de dados com pool de conexões. No final falei sobre como deferimos a execução de operações demoradas pra jobs assíncronos. Alguns iniciantes corretamente se questionaram: por que usar um sistema como o Bull, no caso de Node.js, se existem Promises, Async/Await, Child Processes e Worker Threads? Por que não simplesmente colocar a operação mais demorada numa Promise? Vai rodar em paralelo e devolver o HTML pro usuário rápido. Não é a mesma coisa?</p>
<p>Pra responder isso, eu queria mostrar um outro exemplo. E como escrevi este script tarde da noite, realmente tava morrendo de preguiça de escrever código Javascript então resolvi pedir pro meu copiloto, o ChatGPT, pra escrever pra mim.  Eu peço as coisas pra ele sempre tudo em inglês, porque o modelo em inglês é o mais completo e mais bem treinado e é quem dá as melhores respostas. E minha primeira requisição foi: &ldquo;escreve um form multipart pra upload de uma imagem&rdquo; (&ldquo;write a multipart form to upload an image&rdquo;).</p>
<p>Como era de se esperar, a resposta está correta, eis o código. E ele até dá uma explicação sobre o atributo <code>enctype</code> e tudo mais. Isso é bacana mas não exatamente impressionante. Pra uma lição de casa de curso ou faculdade é suficiente. Mas numa aplicação de verdade não fazemos mais assim.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">form</span> <span class="na">action</span><span class="o">=</span><span class="s">&#34;/upload&#34;</span> <span class="na">method</span><span class="o">=</span><span class="s">&#34;post&#34;</span> <span class="na">enctype</span><span class="o">=</span><span class="s">&#34;multipart/form-data&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">label</span> <span class="na">for</span><span class="o">=</span><span class="s">&#34;image&#34;</span><span class="p">&gt;</span>Select an image to upload:<span class="p">&lt;/</span><span class="nt">label</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;file&#34;</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;image&#34;</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;image&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">button</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;submit&#34;</span><span class="p">&gt;</span>Upload Image<span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">form</span><span class="p">&gt;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>É o seguinte: esse HTML vai gerar um formulário onde o usuário pode escolher uma imagem no seu computador ou smartphone. Quando clicar em &ldquo;enviar&rdquo; o navegador vai fazer aquele caminho que já expliquei nos episódios de rede: criar o pacote de HTTP request, conectar no servidor que responde com o endpoint &ldquo;/upload&rdquo;. E no servidor o processo que recebe essa requisição fica bloqueado esperando receber esse arquivo.</p>
<p>Qual o objetivo de um endpoint de uma aplicação web? É responder de volta pro usuário o mais rápido possível, de preferência na faixa abaixo de 100 milissegundos. Lembram? Quanto mais tempo sua aplicação leva pra responder deixando o usuário pendurado, menos requisições por segundo seu servidor consegue responder, daí somos obrigados a gastar mais, colocando mais servidores pra suportar todo mundo  acessando  simultaneamente. Portanto, sempre precisamos pensar se o que estamos fazendo contribui pra aumentar ou diminuir esse tempo.</p>
<p>O problema do form desse exemplo é que ele se conecta e vai deixar sua aplicação e o usuário parados esperando o upload da imagem completar. Num pior caso, digamos que o usuário é um fotógrafo e resolveu dar upload numa foto em 8K RAW, ou seja, descomprimido, que pode ocupar entre 60 a 100 megabytes! Digamos que seja 60 megabytes, e ele está fazendo upload do celular, numa conexão 4G ruinzinha que dá upload só a 10 megabits por segundo.</p>
<p>Isso vai custar 480 segundos, ou aproximadamente 8 minutos. Isso é terrível. Esse 1 upload fez um processo do seu servidor ficar simplesmente parado, esperando, e sem poder fazer mais nada por 8 minutos inteiros!</p>
<p>O Node tem capacidade de jogar essa espera pra uma thread separada que não bloqueia a habilidade de conseguir responder outras requisições enquanto isso. Porém, imagine que sua aplicação é feita pra fotógrafos que vão ficar o dia todo subindo imagens gigantes desse tipo, o tempo todo. Tudo tem um limite, e em breve seu servidor vai ficar bem pesado, porque não dá pra ficar criando threads infinitamente. E threads não são de graça.</p>
<p>Muitos não entendem isso. Cada nova thread ocupa espaço em RAM e tempo de processamento pra mudar o estado de uma thread pra outra. Um paliativo é justamente usar o NGINX na frente da aplicação Node, ou Spring Boot, ou Laravel ou seja lá qual framework esteja usando, porque o NGINX foi feito pra lidar com coisas como uploads demorados de forma mais eficiente do que o Node. O usuário primeiro se pendura no NGINX, faz o upload, e só quando termina o NGINX passa o arquivo direto pra sua aplicação.</p>
<p>Só que NGINX, apesar de ordens de grandeza mais rápido que seu Node, também não é infinito, e uma hora vai ficar pesado. Tem diversas formas de lidar com esse problema, mas hoje em dia o melhor custo-benefício é simplesmente não lidar com ele. Especialmente porque tem outro problema ruim que não tem a ver com a espera e sim com onde fica o arquivo: o storage, ou HD local do processo que recebeu o arquivo.</p>
<p>Lembra que numa infraestrutura de verdade subimos vários pequenos servidores embaixo de um balanceador de carga? Significa que se a aplicação receber um arquivo e gravar localmente, só os processos no mesmo servidor vão conseguir ler, os outros não vão ter acesso. Mas aí o usuário navega na sua aplicação e vai querer ver a imagem que acabou de subir, e não vai achar, porque o NGINX elegeu outro servidor pra receber sua requisição e nesse servidor não tem a imagem. Como faz?</p>
<p>Podemos usar um volume compartilhado na rede via NFS, que nem quando você se conecta num servidor de arquivos da sua empresa. É uma solução válida. Mas ao mesmo tempo gostaríamos de ter um backup, caso o servidor de arquivos dê algum pau, não queremos perder arquivos dos usuários. E no fim, vamos acabar mandando esses arquivos pra algum lugar como o serviço AWS S3. Por isso falei que o menos trabalhoso é simplesmente não lidar com arquivos localmente e sim já mandar direto pro AWS S3.</p>
<p>Eu já sabia disso então fiz outra requisição pro ChatGPT. Pedi pra ele: &ldquo;agora escreva outra versão desse formulário mas com suporte a upload direto pra um bucket com url pré-assinada do S3&rdquo;. E como esperado, devolveu um exemplo correto. (&ldquo;now write another version of this form but with support for amazon aws s3&rsquo;s pre-signed direct upload to a bucket&rdquo;)</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">form</span> <span class="na">action</span><span class="o">=</span><span class="s">&#34;{{ presignedUrl }}&#34;</span> <span class="na">method</span><span class="o">=</span><span class="s">&#34;post&#34;</span> <span class="na">enctype</span><span class="o">=</span><span class="s">&#34;multipart/form-data&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;hidden&#34;</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;key&#34;</span> <span class="na">value</span><span class="o">=</span><span class="s">&#34;{{ objectKey }}&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;hidden&#34;</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;acl&#34;</span> <span class="na">value</span><span class="o">=</span><span class="s">&#34;private&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;hidden&#34;</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;X-Amz-Credential&#34;</span> <span class="na">value</span><span class="o">=</span><span class="s">&#34;{{ xAmzCredential }}&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;hidden&#34;</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;X-Amz-Algorithm&#34;</span> <span class="na">value</span><span class="o">=</span><span class="s">&#34;AWS4-HMAC-SHA256&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;hidden&#34;</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;X-Amz-Date&#34;</span> <span class="na">value</span><span class="o">=</span><span class="s">&#34;{{ xAmzDate }}&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;hidden&#34;</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;Policy&#34;</span> <span class="na">value</span><span class="o">=</span><span class="s">&#34;{{ policy }}&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;hidden&#34;</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;X-Amz-Signature&#34;</span> <span class="na">value</span><span class="o">=</span><span class="s">&#34;{{ xAmzSignature }}&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">label</span> <span class="na">for</span><span class="o">=</span><span class="s">&#34;image&#34;</span><span class="p">&gt;</span>Select an image to upload:<span class="p">&lt;/</span><span class="nt">label</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;file&#34;</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;image&#34;</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;file&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">button</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;submit&#34;</span><span class="p">&gt;</span>Upload Image<span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">form</span><span class="p">&gt;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Se você é iniciante e pelo menos já tentou programar um upload via web, provavelmente fez igual o ChatGPT na primeira vez: um form multipart dando POST pra um endpoint na sua aplicação. Só que nunca parou pra pensar em coisas como a demora do upload num cenário de verdade e nunca pensou que daria pra dar upload direto pro S3 sem passar pela sua aplicação. O grande truque é o atributo &ldquo;action&rdquo;. No exemplo anterior era um POST pra &ldquo;/upload&rdquo; na sua aplicação. Mas agora é pra URL que estiver nessa variável &ldquo;presignedUrl&rdquo;.</p>
<p>Em seguida tem vários campos escondidos que não chequei na documentação mais atualizada da AWS se é assim mesmo, mas se não for, é parecido. São coisas como o nome da chave onde vai ser gravado esse arquivo no bucket do S3, credenciais e outras configurações. E o resto do formulário é igual. Como falei antes, o  lance é que quando o usuário clicar em &ldquo;submeter&rdquo;, o arquivo não passa por nenhum dos servidores da sua aplicação. Vai dar upload direto pro bucket que você configurou na sua conta antes.</p>
<p>Mas como que gera essa tal de URL pré-assinada? Essa foi minha próxima requisição pro ChatGPT: &ldquo;agora escreva um examplo em Node.js Express de como conseguir a url pré-assinada antes de renderizar o HTML anterior&rdquo;. E ele fez o código de back-end que executa antes de mandar renderizar o HTML que acabei de mostrar. (&ldquo;now write a node.js express example of how to acquire the presignedUrl before rendering the previous HTML&rdquo;)</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">AWS</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;aws-sdk&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">express</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;express&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">express</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">s3</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">AWS</span><span class="p">.</span><span class="nx">S3</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">app</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">&#39;/upload&#39;</span><span class="p">,</span> <span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">objectKey</span> <span class="o">=</span> <span class="sb">`uploads/</span><span class="si">${</span><span class="nb">Date</span><span class="p">.</span><span class="nx">now</span><span class="p">()</span><span class="si">}</span><span class="sb">/</span><span class="si">${</span><span class="nx">req</span><span class="p">.</span><span class="nx">query</span><span class="p">.</span><span class="nx">fileName</span><span class="si">}</span><span class="sb">`</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">params</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">Bucket</span><span class="o">:</span> <span class="s1">&#39;my-bucket&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">Key</span><span class="o">:</span> <span class="nx">objectKey</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">Expires</span><span class="o">:</span> <span class="mi">60</span><span class="p">,</span> <span class="c1">// URL expires in 60 seconds
</span></span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">s3</span><span class="p">.</span><span class="nx">getSignedUrl</span><span class="p">(</span><span class="s1">&#39;putObject&#39;</span><span class="p">,</span> <span class="nx">params</span><span class="p">,</span> <span class="p">(</span><span class="nx">err</span><span class="p">,</span> <span class="nx">presignedUrl</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="nx">err</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">      <span class="k">return</span> <span class="nx">res</span><span class="p">.</span><span class="nx">sendStatus</span><span class="p">(</span><span class="mi">500</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Render form template with presigned URL and other required parameters
</span></span></span><span class="line"><span class="cl">    <span class="nx">res</span><span class="p">.</span><span class="nx">render</span><span class="p">(</span><span class="s1">&#39;upload_form&#39;</span><span class="p">,</span> <span class="p">{</span> <span class="nx">presignedUrl</span><span class="p">,</span> <span class="nx">objectKey</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Nada mau. Ele importa bibliotecas como &ldquo;aws-sdk&rdquo;, o &ldquo;express&rdquo; e tudo mais. Note que só pedi esse trecho, mas pra funcionar, ou precisa saber fazer ou pedir pro ChatGPT configurar seu <code>package.json</code> o arquivo <code>app.js</code> e outras configurações. Só esse código sozinho não funciona. Se nunca usou Express, quando abrir o navegador apontando pra &ldquo;localhost:3000/upload&rdquo;, vai cair nesse <code>app.get('/upload')</code>.</p>
<p>Gerar a chave do objeto pode ser feito de diversas formas. Se for antes do usuário escolher qual arquivo vai mandar, vamos precisar criar um nome aleatório usando coisas como uma função de hash, tipo um SHA512 de um número aleatório. Se for via Javascript a partir do formulário, depois que o usuário escolher o arquivo, podemos usar o próprio nome do arquivo. O principal é escolher uma fórmula que gere um string que vai ser único dentro do bucket, pra não sobrescrever o arquivo de outra pessoa.</p>
<p>Então construímos um JSON simples indicando o bucket, a chave que geramos e por quanto tempo a URL de upload vai ficar disponível. Coisa como 60 segundos ou menos, porque se essa URL não tiver expiração, e vazar, qualquer um poderia ficar fazendo uploads arbitrários no seu bucket até acabar o espaço ou te fazer pagar uma fortuna à toa.</p>
<p>Finalmente, usamos o objeto de s3 que carregamos e chamamos essa função <code>getSignedUrl</code> passando os parâmetros que configuramos. A biblioteca do s3 vai chamar a API da AWS e se não der erro, vai devolver uma URL especial, a tal URL pré-assinada. Que passamos pra esse template &ldquo;upload_form&rdquo; que é o HTML que mostrei antes.</p>
<p>Aliás, faltou uma coisa né? Não tem nada mais ruim pro usuário do que dar upload de um arquivo gigante e não saber se tá fazendo upload devagar ou se travou tudo. Então pedi pro ChatGPT refazer o formulário anterior adicionando uma barra de progresso. &quot;<br>
rewrite the html form with pre-signed url for aws s3 but with a visual progress bar as well&quot;</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">form</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;upload-form&#34;</span> <span class="na">method</span><span class="o">=</span><span class="s">&#34;post&#34;</span> <span class="na">action</span><span class="o">=</span><span class="s">&#34;{{presignedUrl}}&#34;</span> <span class="na">enctype</span><span class="o">=</span><span class="s">&#34;multipart/form-data&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;hidden&#34;</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;key&#34;</span> <span class="na">value</span><span class="o">=</span><span class="s">&#34;{{objectKey}}&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;hidden&#34;</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;x-amz-algorithm&#34;</span> <span class="na">value</span><span class="o">=</span><span class="s">&#34;AWS4-HMAC-SHA256&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;hidden&#34;</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;x-amz-credential&#34;</span> <span class="na">value</span><span class="o">=</span><span class="s">&#34;{{credential}}&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;hidden&#34;</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;x-amz-date&#34;</span> <span class="na">value</span><span class="o">=</span><span class="s">&#34;{{date}}&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;hidden&#34;</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;x-amz-signature&#34;</span> <span class="na">value</span><span class="o">=</span><span class="s">&#34;{{signature}}&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">label</span> <span class="na">for</span><span class="o">=</span><span class="s">&#34;file&#34;</span><span class="p">&gt;</span>Choose file<span class="p">&lt;/</span><span class="nt">label</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;file&#34;</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;file&#34;</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;file&#34;</span> <span class="na">required</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">div</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;progress-bar-container&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">div</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;progress-bar&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">button</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;submit&#34;</span><span class="p">&gt;</span>Upload<span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">form</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">style</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">#</span><span class="nn">progress-bar-container</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">height</span><span class="p">:</span> <span class="mi">20</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">width</span><span class="p">:</span> <span class="mi">200</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">border</span><span class="p">:</span> <span class="mi">1</span><span class="kt">px</span> <span class="kc">solid</span> <span class="mh">#ccc</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">position</span><span class="p">:</span> <span class="kc">relative</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">#</span><span class="nn">progress-bar</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">height</span><span class="p">:</span> <span class="mi">100</span><span class="kt">%</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">width</span><span class="p">:</span> <span class="mi">0</span><span class="kt">%</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">background-color</span><span class="p">:</span> <span class="mh">#4caf50</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">position</span><span class="p">:</span> <span class="kc">absolute</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">top</span><span class="p">:</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">left</span><span class="p">:</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">style</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">form</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s1">&#39;upload-form&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">progressBar</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s1">&#39;progress-bar&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">form</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;submit&#39;</span><span class="p">,</span> <span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">event</span><span class="p">.</span><span class="nx">preventDefault</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">formData</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">FormData</span><span class="p">(</span><span class="nx">form</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">xhr</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">XMLHttpRequest</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">xhr</span><span class="p">.</span><span class="nx">upload</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;progress&#39;</span><span class="p">,</span> <span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">lengthComputable</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="kr">const</span> <span class="nx">percentComplete</span> <span class="o">=</span> <span class="nx">event</span><span class="p">.</span><span class="nx">loaded</span> <span class="o">/</span> <span class="nx">event</span><span class="p">.</span><span class="nx">total</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="nx">progressBar</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">width</span> <span class="o">=</span> <span class="sb">`</span><span class="si">${</span><span class="nx">percentComplete</span> <span class="o">*</span> <span class="mi">100</span><span class="si">}</span><span class="sb">%`</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">xhr</span><span class="p">.</span><span class="nx">open</span><span class="p">(</span><span class="s1">&#39;POST&#39;</span><span class="p">,</span> <span class="nx">form</span><span class="p">.</span><span class="nx">action</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="nx">xhr</span><span class="p">.</span><span class="nx">send</span><span class="p">(</span><span class="nx">formData</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E ele fez de novo uma solução simples, mas correta. Primeiro adicionou um <code>div</code> chamado &ldquo;progress-bar-container&rdquo; com outro <code>div</code> dentro chamado &ldquo;progress-bar&rdquo;.  Em seguida criou um CSS simples pra cada um desses elementos. E no fim fez um Javascript que captura o evento de &ldquo;submit&rdquo; do form pra mandar o upload usando a classe XMLHttpRequest, que tem evento pra indicar o progresso do upload. Ele usa isso pra conseguir o percentual que já foi e atualizar o comprimento do <code>div</code>  da barra via CSS.</p>
<p>Honestamente não testei pra ver se funciona. Só de bater o olho parece que sim, mas de novo, é um código super simplificado. Como  não especifiquei que era pra usar Vue.js ou React ou outro framework, ele fez javascript puro. Se seu projeto usa React, não dê copy e paste desse código. Faça um componente seguindo o padrão da sua equipe. É o tipo de código que se encontraria numa resposta de stackoverflow e que não se deve copiar e colar sem ajustar. Note que pesquisei rapidamente no stackoverflow e achei uma resposta com código similar ao que o ChatGPT me deu. No geral não tá errado.</p>
<p>Estão entendendo? Digamos que você é um programador júnior iniciante num projeto e o gerente pedisse, &ldquo;fulano, faz um formulário de upload de imagens no nosso site?&rdquo;. Você faria a mesma coisa que o ChatGPT: aquele primeiro formulário HTML multipart mandando o arquivo direto pra aplicação e gravando no disco local do servidor e pronto. Se a mesma requisição viesse pra mim ou outro programador mais experiente, faríamos perguntas: &ldquo;é pra coisas pesadas? quem vai usar, só pessoal interno ou é pra todo usuário? onde quer gravar essas imagens? podemos mandar direto pro s3? quer mostrar uma barra de progresso pro usuário?&rdquo;</p>
<p>O ChatGPT sabe como fazer tudo isso, mas ele não questiona, faz o mais simples primeiro, e não tá errado. Precisamos saber de antemão que queremos mandar pro S3 via URL pré-assinada, daí ele faz exatamente o que foi pedido. Deixa eu dar outro exemplo pra ficar mais claro e aproveitar pra explicar mais conceitos que iniciantes não conhecem. Agora que temos a imagem no bucket do S3, digamos que nossa aplicação web é tipo um clone de Instagram. Eu gostaria que a imagem fosse cropada em formato quadrado e sempre fosse gravado em formato PNG pra padronizar tudo.</p>
<p>Então pedi exatamente isso pro ChatGPT: &ldquo;agora escreva um exemplo de ação de controller de Node.js Express pra puxar a imagem que deu upload na url pré-assinada do S3, daí use ImageMagick pra cropar num quadrado perfeito, salvar como um PNG e dar upload pra outro bucket do S3&rdquo;. (&ldquo;now write an example of a node.js express controller action to fetch the image that was uploaded to the presigned url in aws s3, then use imagemagick to crop it in a perfect square, save as a PNG and upload to another s3 bucket&rdquo;)</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">AWS</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;aws-sdk&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">express</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;express&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">express</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">s3</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">AWS</span><span class="p">.</span><span class="nx">S3</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">im</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;imagemagick&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">app</span><span class="p">.</span><span class="nx">post</span><span class="p">(</span><span class="s1">&#39;/process-image&#39;</span><span class="p">,</span> <span class="kr">async</span> <span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">try</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// Download image from S3 bucket
</span></span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">objectKey</span> <span class="o">=</span> <span class="nx">req</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">objectKey</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">params</span> <span class="o">=</span> <span class="p">{</span> <span class="nx">Bucket</span><span class="o">:</span> <span class="s1">&#39;my-bucket&#39;</span><span class="p">,</span> <span class="nx">Key</span><span class="o">:</span> <span class="nx">objectKey</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">imageData</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">s3</span><span class="p">.</span><span class="nx">getObject</span><span class="p">(</span><span class="nx">params</span><span class="p">).</span><span class="nx">promise</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">imageBuffer</span> <span class="o">=</span> <span class="nx">imageData</span><span class="p">.</span><span class="nx">Body</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Use ImageMagick to crop the image into a square
</span></span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">croppedImageBuffer</span> <span class="o">=</span> <span class="kr">await</span> <span class="k">new</span> <span class="nb">Promise</span><span class="p">((</span><span class="nx">resolve</span><span class="p">,</span> <span class="nx">reject</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">im</span><span class="p">.</span><span class="nx">crop</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">        <span class="nx">srcData</span><span class="o">:</span> <span class="nx">imageBuffer</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">width</span><span class="o">:</span> <span class="mi">200</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">height</span><span class="o">:</span> <span class="mi">200</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">gravity</span><span class="o">:</span> <span class="s2">&#34;Center&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span> <span class="p">(</span><span class="nx">err</span><span class="p">,</span> <span class="nx">stdout</span><span class="p">,</span> <span class="nx">stderr</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="k">return</span> <span class="nx">reject</span><span class="p">(</span><span class="nx">err</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="nx">resolve</span><span class="p">(</span><span class="nx">stdout</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">      <span class="p">});</span>
</span></span><span class="line"><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Save the cropped image as a PNG
</span></span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">pngImageBuffer</span> <span class="o">=</span> <span class="kr">await</span> <span class="k">new</span> <span class="nb">Promise</span><span class="p">((</span><span class="nx">resolve</span><span class="p">,</span> <span class="nx">reject</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">im</span><span class="p">.</span><span class="nx">convert</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="p">[</span><span class="s1">&#39;-strip&#39;</span><span class="p">,</span> <span class="s1">&#39;-interlace&#39;</span><span class="p">,</span> <span class="s1">&#39;Plane&#39;</span><span class="p">,</span> <span class="s1">&#39;-quality&#39;</span><span class="p">,</span> <span class="s1">&#39;100&#39;</span><span class="p">,</span> <span class="s1">&#39;-colorspace&#39;</span><span class="p">,</span> <span class="s1">&#39;sRGB&#39;</span><span class="p">,</span> <span class="s1">&#39;-define&#39;</span><span class="p">,</span> <span class="s1">&#39;png:compression-level=9&#39;</span><span class="p">,</span> <span class="s1">&#39;png:-&#39;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="nx">croppedImageBuffer</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="nx">err</span><span class="p">,</span> <span class="nx">stdout</span><span class="p">,</span> <span class="nx">stderr</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="k">if</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="nx">reject</span><span class="p">(</span><span class="nx">err</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">          <span class="p">}</span>
</span></span><span class="line"><span class="cl">          <span class="nx">resolve</span><span class="p">(</span><span class="nx">stdout</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Upload the cropped image to another S3 bucket
</span></span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">targetBucket</span> <span class="o">=</span> <span class="s1">&#39;cropped-images&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">targetObjectKey</span> <span class="o">=</span> <span class="nx">objectKey</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="s1">&#39;uploads/&#39;</span><span class="p">,</span> <span class="s1">&#39;cropped/&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kr">await</span> <span class="nx">s3</span><span class="p">.</span><span class="nx">putObject</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">      <span class="nx">Bucket</span><span class="o">:</span> <span class="nx">targetBucket</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">Key</span><span class="o">:</span> <span class="nx">targetObjectKey</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">Body</span><span class="o">:</span> <span class="nx">pngImageBuffer</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">ContentType</span><span class="o">:</span> <span class="s1">&#39;image/png&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">}).</span><span class="nx">promise</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nx">res</span><span class="p">.</span><span class="nx">sendStatus</span><span class="p">(</span><span class="mi">200</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="nx">error</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">res</span><span class="p">.</span><span class="nx">sendStatus</span><span class="p">(</span><span class="mi">500</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>De novo, tá certinho. No começo ele carrega bibliotecas que precisa saber carregar no <code>package.json</code>. E cria um endpoint de POST chamado &ldquo;/process-image&rdquo; que poderia ser chamado pelo Javascript do formulário, pelo objeto de XMLHTTPRequest quando detectar que o upload terminou, por exemplo. Esse código faz o seguinte, primeiro tem que saber a URL completa do arquivo que acabou de subir no bucket do S3, e configurar os parâmetros pra esse bucket.</p>
<p>Ele começa chamando o método <code>getObject</code> do objeto do s3. Não estou familiarizado com a biblioteca <code>aws-sdk</code> pra Javascript, mas parece que  devolve uma Promise e corretamente dá <code>await</code>. Pra quem é de outras linguagens que não tem Promises, não é a mesma coisa, mas é como se estivesse colocando esse <code>s3.getObject</code> dentro de uma nova Thread e esse <code>await</code> é como se fosse <code>join</code>, ou seja, pra esperar a thread terminar antes de continuar.</p>
<p>Finalmente, puxamos o conteúdo binário do arquivo nessa variável <code>imageBuffer</code>. Relembrando, se a imagem tinha 60 megabytes, agora tem 60 megabytes na memória do interpretador V8 desse processo. Na sequência, ele faz o código pra fazer crop, ou seja, pra recortar a imagem num quadrado usando a biblioteca ImageMagick nesse &ldquo;im&rdquo; chamando o método &ldquo;crop&rdquo; direitinho.</p>
<p>Ele assumiu que eu queria uma imagem de 200 por 200 pixels. Tá ok, não tinha dito nenhum outro número, mas de novo, por isso que não pode copiar e colar às cegas. Se o amador só copiasse sem ver e deixasse isso passar, agora aquela imagem de altíssima qualidade em 8K RAW do fotógrafo, se transformou numa imagem porcaria minúscula. Se for pra ser fixo eu não cortaria nada menor que uns 1000 por 1000 pixels.</p>
<p>Esse pattern que ele faz de englobar tudo numa nova Promise e imeditamente chamar await, do ponto de vista só desse código, seria a mesma coisa que não ter isso. Desse jeito (muda imagem). Esse código vai esperar, await, até o crop terminar. Pela forma como o event loop do Node.js funciona, suponho que a vantagem é que fazendo assim, a única thread do event loop ficaria bloqueada e o Node não iria conseguir fazer mais nada até o crop do ImageMagick terminar.</p>
<p>Mas colocando numa Promise e dando await, ele delega pro sistema operacional a operação de esperar o I/O (ler arquivo, gravar arquivo), e libera o event loop pra fazer outra coisa, como responder outras requisições de outros usuário. Quando a operação cara de I/O retorna, a thread continua depois do await como se nada tivesse acontecido. Essa é a grande vantagem de se usar Node em primeiro lugar, o event loop e o suporte a Promises.</p>
<p>Vou voltar nisso mais pra frente. Em seguida temos um bloco de código que chama direto o programa &ldquo;convert&rdquo;. Mesma coisa, Promise com await. E no final, o último bloco é chamando a biblioteca do S3 pra fazer o upload do binário cropado e reformatado como PNG pro bucket do S3.</p>
<p>Agora vamos entender o problema. Quer dizer, não é nenhum problema se só você for usar essa aplicação, ou mesmo se for uma parte administrativa de um site onde só meia dúzia de pessoas for usar. Mas se for pra todo usuário do site e tiver centenas ou milhares de usuários, isso vai pesar rapidamente e exigir que se faça deploy de muitos servidores, tornando a infraestrutura super cara.</p>
<p>Primeiro de tudo. Quando fazemos download da imagem do S3 e guardamos nessa variável <code>imageData</code> e a imagem tem 60MB, de cara estamos consumindo 60MB de RAM do servidor. Digamos que 100 usuários estão fazendo upload de imagens mais ou menos ao mesmo tempo. Seria 6 GB de RAM sendo ocupado no servidor web. Pra processar imagens, realmente vai usar muita RAM, o problema é compartilhar isso no mesmo servidor que precisa responder requisições web o mais rápido possível.</p>
<p>Além disso temos duas operações de rede pra s3, de download e upload, baixando uns 60MB e subindo uma imagem menor, mas é um tráfego que não podemos ignorar. De novo, se tiver 100 usuários simultaneamente subindo imagens de alta qualidade, naquele minuto seu servidor tá precisando gastar uns 6 gigabytes de tráfego só pra baixar do S3. &ldquo;Ah, mas é porque você mandou fazer direct upload pro s3, se estivesse num storage local estaria ocupando menos banda&rdquo;</p>
<p>Sim, é verdade e eis um exemplo de que não tem resposta certa. Se você é um serviço grande onde uma das partes principais do que oferece aos usuários é armazenamento de arquivos grandes, talvez  queira ter essa infraestrutura sob seu controle. Vai ter gente dedicada, com servidores no data center dedicados só pra gerenciar armazenamento, daí não precisa usar tanto S3.</p>
<p>Por outro lado, pode ser uma empresa no começo, que ainda não tem  tantos servidores, rede e gente de infra dedicada só pra armazenamento. Talvez o S3 seja um custo-benefício melhor. Nenhuma das duas respostas é errada, depende de quem você é, qual seu nível de serviço, seu orçamento, quanto cobra e qual sua receita.</p>
<p>Digamos que somos uma empresa menor, e pra gente S3 faz sentido, então o código seria mais ou menos como nesse exemplo. Mas além do armazenamento temos esses dois blocos pesados de processamento, que é calcular o cropping e converter a image pra outro formato. Lembra que falei que na variável <code>imageData</code> tem 60 megabytes do binário da imagem original? Nessa parte aqui a variável <code>croppedImageBuffer</code> vai ter o binário da imagem recortada, digamos que seja uns 50 Megabytes.</p>
<p>E nessa outra parte aqui onde convertemos pra PNG faz de conta que o PNG ficou com uns 5 Megabytes de tamanho, porque é comprimido. Então, antes dessa função acabar, essas 3 variáveis estão ocupando 60 + 50 + 5, ou seja, 115 megabytes de memória. Se tiver 10 usuários pendurados fazendo upload de imagens semelhantes, estamos ocupando uns 11 giga e meio de RAM dos servidores só nesse processo.</p>
<p>Estão entendendo? O problema não é só conseguir rodar essas tarefas em paralelo pra liberar processamento pro Node conseguir atender mais requisições. O problema é que quanto mais gente usar o sistema, mais rápido vai acabar os recursos das máquinas web e em breve não vai ter RAM pra atender mais gente chegando. O objetivo é responder rápido, mas fazendo tudo isso, é impossível devolver a resposta HTML pro usuário na faixa de menos de 100 milissegundos.</p>
<p>Por isso que pra casos assim, o correto é jogar esse processamento pra fora da aplicação web. Isso não vai diminuir nem a carga de processamento e nem o uso de RAM, mas vamos conseguir segmentar, separar os recursos dedicados só pra web e os recursos dedicados pra processar as imagens. No caso do Node.js podemos usar a biblioteca Bull, como mencionei no último episódio.</p>
<p>Pedi pro ChatGPT: &ldquo;escreva o mesmo código mas sem usar Promises e na realidade deferir as tarefas de download da imagem, cropping, salvar em PNG e fazer upload de volta pra outro bucket como um job assíncrono implementado como um worker de Bull. (&ldquo;write the same code but without using Promises and actually deferring the tasks for downloading the image, cropping the image, saving as PNG and uploading back to a bucket to an asynchronous job implemented as a Bull worker&rdquo;)</p>
<p>Sabendo perguntar em detalhes exatamente o que queremos, o ChatGPT responde direitinho. Ele não tem como adivinhar qual é a melhor solução pra você, por isso sempre vai dar a resposta mais simples. Se eu não soubesse que Bull existe, o ChatGPT não ia me dizer. Mas agora o código começa com ele carregando a biblioteca do Bull e instanciando uma nova fila chamada &ldquo;image-processing&rdquo;. Em seguida já adiciona uma nova tarefa na fila com a chave do objeto no bucket, que é a imagem original que o usuário fez upload. Esse é o trecho que iria no controller do Express e vamos tirar fora.</p>
<p>Eu não pedi pro ChatGPT reescrever aquele trecho <code>app.post(&quot;/upload&quot;)</code> do Express, mas seria arrancar fora todo aquele código do processamento de imagens e simplesmente colocar essa linha de registrar uma nova tarefa na Fila e rapidamente devolver OK pro usuário parar de esperar na fila. Caso a página web que iria devolver tivesse um preview da imagem que subiu, não teríamos como mostrar porque ainda não processamos, mas aí é só colocar uma imagem genérica com uma mensagem de &ldquo;aguarde, estamos processando&rdquo;, e colocar um WebSocket ou algo assim pra puxar a imagem processada quando ficar disponível depois.</p>
<p>Voltando pro Worker de Bull, é a mesma coisa de antes, o código que acabamos de apagar do endpoint de <code>app.post</code>. Esse worker eu instalaria num servidor separado só pra workers. A vantagem é que  posso controlar quantos tarefas quero processar simultaneamente. Digamos que meu servidor não tenha 11 gigabytes de RAM que seria necessário pra rodar 100 tarefas simultâneas de imagens grandes. Só tenho 4 gigabytes, então só dá pra rodar umas 30 tarefas ou menos. Mas tudo bem. Vai demorar mais, mas como o usuário não tá pendurado esperando e nem estou congestionando os servidores web, não tem problema se demorar o triplo de tempo.</p>
<p>Além disso tem outra razão pra jogar essas coisas pesadas num job assíncrono em vez de tentar rodar com Promises ou Threads. Digamos que tenha um bug na biblioteca do ImageMagick ou do S3 e dê um crash na hora de processar por alguma razão. Como faço pra rodar de novo essa tarefa? Existem bibliotecas como o &ldquo;async-await-retry&rdquo;, mas imagina que seja um erro sistêmico, e tinha 100 usuários subindo imagens e por um erro qualquer, as 100 tarefas comecem a dar pau, e 100 vezes o sistema tente dar retry e fique retentando? Meu servidor vai morrer retentando à toa assim. Eu preciso ter controle sobre regras de retry.</p>
<p>Numa fila como Bull, se os workers começam a dar pau, posso configurar pra não dar retry e só marcar erro. Se descobrimos que foi algo sistêmico, podemos corrigir o bug e depois que estiver tudo corrigido, mandar as tarefas pendentes na fila rodar, tudo sob controle. Na documentação do Bull ele explica como podemos configurar retries automáticos, por exemplo, aqui ele diz pra tentar 3 vezes caso dê problema e com um tempo de backoff exponencial que começa em 1 segundo na primeira tentativa, 2 segundos pra segunda, 4 segundos pra terceira. Nunca é bom retentar imediatamente porque muito provavelmente vai dar problema igual.</p>
<p>E o Bull tem um monte de funcionalidades importantes como Gracefull shutdown. Digamos que modificamos o código dos workers e agora precisamos atualizar nos servidores. Não pode simplesmente desligar tudo e atualizar. Não queremos crashear jobs que estão rodando. Normalmente queremos que o worker saiba que tem uma atualização e reinicie só depois que terminar de processar o job que estava em andamento.</p>
<p>Se temos vários servidores de Bull, cada um com vários processos, ele vai desligando, atualizando e reiniciando um a um só depois que terminar os jobs. Assim nenhum job vai corromper. É que nem dar shutdown normal num Windows em vez de apertar o botão de força e desligar forçado, que nunca é bom.</p>
<p>E sendo um sistema com fila e workers separados, existem projetos como o Matador, que é uma interface web pra monitorar os trabalhos na fila do Bull. Assim um administrador ou devops pode checar visualmente se os workers estão se comportando como deveriam e facilitar conseguir identificar se tudo está correndo bem.</p>
<p>Entendam, colocar um Bull e jogar aquele processamento pesado de imagens em outros servidores não diminui a quantidade de recursos sendo gastos. Ele serve pra tornar esse processamento mais gerenciável e previsível. Tudo que roda no lado dos servidores web é pra ser leve e rápido. Tudo que é pesado, como nesse exemplo de imagens, jogamos em jobs em outros servidores que só rodam jobs.</p>
<p>E a comunicação entre os dois é feita via uma Fila. No caso do Bull ele usa um módulo chamado <code>ioredis</code> que usa Redis como servidor de fila. O correto na hora de instanciar a fila é indicar o servidor do Redis na conexão e quando subimos os workers, apontamos pro mesmo Redis pra escutarem quando chegar novos jobs, como estou mostrando neste exemplo que tem na própria documentação.</p>
<p>Se não ficou claro vou repetir: quando precisamos executar alguma tarefa mais pesada, faz de conta, mandar uma linha nova de log pra um servidor de log, enviar uma notificação, coisas que não duram muitos segundos ou até minutos, é possível que Promises no caso de Javascript, ou async/await num DotNet ou mesmo Threads como num Java, façam sentido. O problema é quando a duração desse processamento é longo e, pior, varia bastante. Nesse caso o tempo vai variar dependendo do tamanho das imagens. Fica imprevisível demais pra controlar misturado com a requisição web.</p>
<p>O certo é mandar pra jobs assíncronos. Isso me lembrou um caso. Esses dias eu estava configurando minha conta de Google Workspace, que são os planos pagos do Google pra serviços como Gmail. Eu queria habilitar a opção de conseguir mudar o tema do meu Gmail, isso tava desligado sei lá porque. Fui no admin, nessa tela e liguei. Mas mesmo recarregando meu Gmail, essa opção não aparecia. Olha só, em outra conta de Google Workspace que é da minha empresa, já tá ligado e em configurações aparece essa opção aqui tão vendo? Por que na outra conta não apareceu?</p>
<p>Porque quando habilitei no admin ele mandou isso pra uma fila que tem um tempo super longo pros workers pegarem. Pode ser porque tem tanta operação em fila dos milhões de usuários que usam a plataforma, que precisa de horas pra executar meu pedido. Você imaginaria que deveria ser imediato, mas eles escolheram separar assim porque deve ser pesado por N motivos que não temos acesso de saber.</p>
<p>Veja o YouTube. Toda vez que termino um episódio, faço upload do arquivo de video. É um arquivo de dezenas de gigabytes, leva vários minutos pra subir. E depois que sobe precisa fazer checagens de copyright, gerar versões com resoluções menores, sincronizar cópias em CDN e assim por diante. Enquanto isso não acontece, eu fico vendo essa mensagem indicando que ainda tá processando. Só depois que termina que consigo fazer coisas como adicionar cards.</p>
<p>Veja no anchor.fm, que é onde eu subo o arquivo mp3 pra publicar no Spotify ou Deezer. Eu faço upload e depois ele manda pra alguma fila, enquanto isso no meu front-end só indica que tá &ldquo;processando&rdquo;. Meu navegador não tá pendurado no servidor deles. Provavelmente tem um javascript que de tempos em tempos checa se já terminou, ou tem um web socket aberto.</p>
<p>Qual &ldquo;a&rdquo; melhor forma de fazer tudo isso? Não tem. Depende de quantos servidores, de quais tamanhos você pode pagar. O software tem que ser feito pra organizar os recursos disponíveis de forma que fique tudo menos difícil de gerenciar. Não quer dizer necessariamente mais performance ou rodar tudo em paralelo. Controle significa que quando alguma coisa dá erro eu tenha a oportunidade de pausar, checar, consertar e mandar rodar de novo, e pra isso soluções usando fila e workers como o Bull de Node.js faz sentido.</p>
<p>Muitos pensam que porque Node.js tem eventloop não precisa de jobs assíncronos, só Promises. Espero que tenham entendido que são funcionalidades diferentes pra casos diferentes. Mesma coisa pessoal de Java, que tem threads nativas e poderiam achar que não precisam de jobs assíncronos. Mas um dos sistemas de fila mais reconhecidos é justamente o Apache Kafka, que é feito em Java. Aproveitei pra pedir pro ChatGPT reescrever o worker de Bull em Spring Boot (&ldquo;To process an image that was uploaded to an Amazon S3 bucket using a pre-signed URL in a Spring Boot app, using a Kafka queue to defer the tasks to a worker, you can use the following code&rdquo;)</p>
<p>Tecnicamente, o boilerplate, a parte burocrática de configuração de Kafka exige adicionar o pacote org.springframework.kafka no arquivo <code>pom.xml</code> do projeto que é mais ou menos o equivalente ao <code>package.json</code> de Node. Daí teria que usar o script <code>kafka-topics.sh</code> pra criar os tópicos da fila. Finalmente, pra aplicação Spring Boot conseguir criar mensagens pra mandar pra fila, precisa configurar um <code>ProducerFactory</code> que é o responsável pela estratégia de como criar instâncias de Producers de Kafka.</p>
<p>O código de exemplo que o ChatGPT gerou instancia um KafkaTemplate mas ele deveria injetar um ProducerFactory dentro, que foi omitido. Como o video não é pra ser um tutorial de Spring Boot, depois leiam a documentação, mas só pra explicar que, de novo, o ChatGPT não me deu tudo mastigado. Eu sei que falta coisa porque já li a documentação. Se você nunca usou Kafka na vida e achou que o ChatGPT ia te dar tudo mastigado, pensou errado.</p>
<p>Em seguida ele cria um endpoint web chamado &ldquo;/process-image&rdquo; como tínhamos feito com o Express. No Spring Boot isso é configurado com uma annotation que é essa linha que começa com arroba pra decorar a função logo abaixo. E essa função responde super rápido porque a única coisa que faz é pegar a instância de KafkaTemplate e enviar a mensagem do job pra fila.</p>
<p>A classe de Worker abaixo também é configurado com outra annotation pra dizer que é o responsável por mensagens enviadas pro tópico &ldquo;image-processing&rdquo;. Nessa classe tem a função <code>processImage</code> que vai fazer a mesma coisa que o worker de Bull, só que em Java. Instanciar bibliotecas de S3 e ImageMagick nas versões de Java, download da imagem num ByteArray em memória, cropar e converter em PNG e fazer upload. Em termos de tempo de processamente, como o grosso depende da conexão remota com o S3 e de chamar o mesmo ImageMagick, tanto faz ser em Javascript ou em Java, vai levar mais ou menos o mesmo tempo pra rodar.</p>
<p>Pra quem gosta de perder tempo discutindo benchmarks de linguagens em subreddit, entenda esse cenário. Veja esses dois códigos lado a lado. A linguagem meio que não importa, porque o grosso do trabalho não é feito nem em Javascript e nem em Java. O grosso é o ImageMagick e o S3. Nas dus versões, vão fazer fork e subir um processo separado da ferramenta <code>convert</code> que vem no pacote do ImageMagick, que é feito em C.</p>
<p>Ele é quem vai fazer o trabalho sujo. E sim, eu não precisava ter usado ImageMagick, poderia ter escolhido uma biblioteca feita em puro Javascript e outra feita em puro Java. Mas não vejo porque, ImageMagick é a ferramenta mais madura e mais reconhecida pra trabalhar imagens. É literalmente um Photoshop em linha de comando que faz praticamente tudo. Pra que vou reinventar a roda? E mesmo se eu reinventasse a roda, não seria uma diferença muito grande de qualquer jeito.</p>
<p>Novamente, o objetivo de hoje foi demonstrar como um código feito como exercício simples, que só você vai usar sozinho na sua própria máquina é de um jeito. Mas como as mesmas funcionalidades num projeto de verdade ficam completamente diferentes. Na sua própria máquina, não precisa de Bull nem de Kafka. Basta subir uma Promise, ou child process que é fork de processos, ou worker thread que sobe uma thread no mesmo processo pra rodar em paralelo. Mesmo se a imagem for pesada, só você tá usando, mesmo se demorar alguns minutos e usar muita RAM e cair pro swap que é mais lento, não tem problema. O problema só aparece se estiver exposto na internet, aí fodeu.</p>
<p>E agora sobre coisas como o ChatGPT ou GitHub Copilot que não mostrei nesse video mas já usei. Acho excelentes. Eu sei exatamente o que quero, eu sei que não quero uma solução simples com upload direto na aplicação web. Eu sei que existe a técnica de URL pré-assinada, então o ChatGPT me obedece. Eu sei que o processamento de imagens, download e upload pro S3 é demorado, por isso eu soube pedir pro ChatGPT fazer o código levando isso em consideração e usar Bull e Kafka.</p>
<p>Além disso eu sei que nos exemplos de código que o ChatGPT me devolveu não tem a configuração de Redis do Bull nem do ProducerFactory de Kafka. Eu sei onde configurar e os detalhes que não lembro. Eu sei achar a documentação e completar. Também poderia pedir. &ldquo;Ow, ChatGPT, faltou a configuração de ProducerFactory no KafkaTemplate, pode completar pra mim?&rdquo;. Entenderam? Vocês assistindo que são iniciantes e ainda não trabalharam em projetos de verdade: já tinham ouvido falar dessas coisas?</p>
<p>Não estou dizendo que ninguém ensina, mas entenda: os cursos mais básicos querem que vocês tenham a falsa ilusão que programar é simples e qualquer um pode fazer. E te dão um passo a passo pra conseguir fazer um front-end de instagram, com upload de imagem, e quando conseguir ver a imagem postada na sua aplicação local, fica feliz. E realmente, a primeira vez que consegue é bacana mesmo. Mas acho que falta dizer que em cima disso tem esse tanto de coisas que mostrei nesse video e no anterior.</p>
<p>Uma inteligência artificial consegue facilmente substituir um programador júnior inexperiente como puderam ver aqui. Porém, ele ainda não consegue substituir um programador experiente. Eu vou ficar impressionado quando pedir pra ele fazer alguma coisa e em vez de me dar um código simples, começar a me devolver perguntas pertinentes, como quantos usuários eu espero que o sistema tenha que aguentar, ou o volume dos uploads. Um programador experiente não sai codando à toa de qualquer jeito.</p>
<p>O ChatGPT é um serviçal bem burrinho que dá respostas erradas ou incompletas com muita convicção. Por exemplo, num video que eu tava assistindo, do Joseph Carlson, ele pediu pro irmão dele, que é médico de verdade, fazer uma pergunta pro ChatGPT, no caso foi &ldquo;qual a condição mais comum que provavelmente se origina na coróida e que afeta homens jovens?&rdquo; E o ChatGPT faz um textão explicando que é neovascularização coroidal e segue explicando o que é isso. E o médico responde: &ldquo;nada mau, mas está incorreto. Seria correto se estivéssemos falando de pessoas idosas. A resposta correta seria retinopatia serosa, e é por isso que fazer auto diagnóstico via Google  não funciona&rdquo;.</p>
<p>Claro, esse caso em particular, com um pouco mais de treino o ChatGPT deve conseguir responder correto da próxima vez, mas o ponto é que as respostas deles não são garantidamente corretas o tempo todo e quando são erradas, ele não tem noção que tá errado. A resposta é sempre verbalizada de uma forma bem assertiva. Se você não for também especialista no assunto, vai acreditar e nesse caso poderia se auto-diagnosticar errado e acabar tendo problemas graves. Mas isso já acontece hoje com Google também.</p>
<p>Pra amadores, o ChatGPT pode ser usado como uma ferramenta de estudos, e nada do que ele soltar deve ser usado no mundo real sem consultar um especialista, porque você não tem o conhecimento e nem a experiência pra saber se veio errado ou incompleto. Pra especialistas de verdade, o ChatGPT é excelente pra fazer as tarefas mundanas que nós sabemos fazer, mas não vale nossa taxa hora, como os códigos que mostrei aqui. Eu sei avaliar se o código serve ou não, por isso pra mim o ChatGPT funciona bem. Quando ele não sabe responder, ou me responde errado, eu sei como consertar.</p>
<p>O banco de dados, o modelo dele, é realmente impressionante. O que mais me impressiona não é funcionar. Eu sei que funciona. As teorias pra isso existem e vem sendo evoluídas e desenvolvidas faz décadas. Mas pra fazer modelos desse tipo custa extremamente caro. Eu estava assistindo o canal Coldfusion que gosto muito e recomendo, e no video ele menciona o que o Sam Altman, presidente da OpenAI  falou.</p>
<p>Hoje temos milhões de pessoas usando o ChatGPT pela novidade. Mas ele não escala pra ordem de bilhões de pessoas, que seria a escala de redes sociais como Facebook ou a escala que Google, Apple ou Microsoft conseguem atingir com seus produtos. O custo seria inviável. Segundo o Altman, uma resposta do ChatGPT custa de 10 a 100 vezes o custo de pesquisa no Google. Ainda precisa otimizar 100 vezes essa tecnologia pra chegar num ponto onde vai ser economicamente viável todo mundo usar no dia-a-dia, como smartphones ou um Google.</p>
<p>E claro, em alguns anos isso vai acontecer. Hoje ainda não é esse dia. Pode levar 10 anos, 20 anos. E isso pra otimizar custos. Pra chegar num ponto onde posso confiar quase 100% a ponto de não precisar de especialistas humanos, isso realmente não vejo acontecendo tão cedo. Eu vejo sim, sendo um excelente auxiliar pra nós especialistas, e num espectro menor, ajudando não especialistas com perguntas simples do dia a dia, algo um pouco melhor que as porcarias de Alexa, Siri ou Cortana.</p>
<p>Pra nós programadores tem outro aspecto. Muitos iniciantes acham que deveriam estudar tópicos de inteligência artificial porque num futuro próximo todo mundo vai estar programando inteligências artificiais, mas sinto te informar que isso não vai acontecer como você pensa. Sim, se você tem nível e talento pra ser contratado pra times de pesquisa da OpenAI, da Microsoft, da Apple, do Google, aí você teria chances de trabalhar na construção dessas inteligências. Mas fora dessas empresas gigantes, não vai rolar.</p>
<p>O maior problema não são os algoritmos. O problema é o treinamento. Pra conseguir fazer um Dall-e 2, Midjourney ou Stable Diffusion gerar fotos e desenhos artísticos realistas, precisou treinar cada um desses modelos com milhões de imagens diferentes por muito e muito tempo. Estamos falando em escala de petabytes de dados, milhares ou milhões de horas em dezenas ou centenas de servidores super caros. Data centers dedicados com o supra sumo das placas de video NVIDIA. Ou seja, você, no seu PCzinho ou notebook, jamais vai conseguir produzir um modelo de verdade que vá além de um  brinquedo.</p>
<p>Modelos sérios do tamanho de um ChatGPT custam milhões de dólares pra produzir. Você não vai ter acesso a esse tipo de investimento e infraestrutura. Se alguém de uma tech startup desconhecida vier te convidar pra construir um modelo desses, já sabemos que ele mora em Nárnia, enxerga unicórnios e tem amigos imaginários. Não vai acontecer.</p>
<p>Na prática, quase todos nós, programadores, no máximo vamos poder usar modelos prontos dos outros, como da OpenAI. Se já tentou usar o ChatGPT todo dia, já deve ter visto como do nada uma resposta engasga e dá erro de conexão, ou como o site todo às vezes fica fora do ar por alguns segundos ou minutos. Exatamente porque nesta versão de agora, ainda não escala. Precisa sair uma versão 2.0 com mais garantias pra gente poder investir em produtos que tirem proveito desse modelo.</p>
<p>Mas a pergunta que todo mundo tem na cabeça é &ldquo;o ChatGPT vai substituir programadores?&rdquo; E a resposta é simples: sim, vai substituir os ruins, os preguiçosos, os falsos sêniors e infelizmente alguns dos iniciantes. Porque muitas tarefas mundanas de código que um júnior pegaria, eu como sênior posso usar o ChatGPT pra fazer mais rápido e, mais importante, com zero reclamação, mais rápido e com menos erros. Agora, substituir um bom sênior de verdade? Eu diria que precisa pelo menos de uns bons 50 anos antes de cogitar algo assim. Tá longe, bem longe.</p>
<p>Eu estou satisfeito o suficiente com ferramentas como o ChatGPT ou Copilot do GitHub. Eles são mais eficientes e mais rápidos do que dar alt-tab e procurar o que preciso no Google ou Stackoverflow. Eu sei quando ele me devolve bobagem e consigo ignorar. E sei quando tá incompleto. Ao mesmo tempo, é bom pra fazer coisas mudanas e repetitivas, como testes unitários por exemplo. Ou me devolver exemplos de códigos que estou com preguiça de procurar no Github. Realmente, é como se eu tivesse um serviçal que procura no Google pra mim.</p>
<p>Muitos YouTubers tem feito video dizendo &ldquo;olha só como o ChatGPT escreveu o script desse video&rdquo;. Eu tentei, mas tava dando tanto trabalho que foi mais r;apido eu mesmo escrever. Sinto muito, na atual versão, o ChatGPT é incapaz de escrever um script pra este canal sem muita intervenção. Eu concluo que os canais dos outros são bem superficiais e simplórios se um script gerado pelo ChatGPT é capaz de enganar a audiência. No meu caso não rolou. O ChatGPT é excelente pra encher linguiça de forma convincente, mas falar direto ao ponto, ele tem muita dificuldade.</p>
<p>No final a conclusão é simples: seu grau de empolgação com o ChatGPT é inversamente proporcional ao seu nível de conhecimento. Se for baixo, vai ficar exageradamente empolgado, se for alto, vai achar útil, com ressalvas. É a diferença de quem vê um truque de mágica pela primeira vez, e de quem já sabe como o truque funciona. Pra quem não quer ser substituído por uma IA mande dúvidas nos comentários abaixo. Se curtiram o video deixem um joinha, assinem o canal e não deixem de compartilhar o video com seus amigos. A gente se vê, até mais!</p>
]]></content:encoded><category>sistemas-distribuidos</category><category>backend</category><category>llms</category><category>akitando</category></item><item><title>[Akitando] #133 - Tornando sua App Web Mais Rápida! | 4 Técnicas de Otimização</title><link>https://akitaonrails.github.io/2022/12/12/akitando-133-tornando-sua-app-web-mais-rapida-4-tecnicas-de-otimizacao/</link><guid isPermaLink="true">https://akitaonrails.github.io/2022/12/12/akitando-133-tornando-sua-app-web-mais-rapida-4-tecnicas-de-otimizacao/</guid><pubDate>Mon, 12 Dec 2022 09:30:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/KyqFXVVgvIs"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;Por que a aplicação que você tem feito vendo tutoriais e cursos online é BEM diferente do que equipes profissionais realmente fazem de verdade? Por que só &amp;ldquo;estar funcionando&amp;rdquo; não é o suficiente?&lt;/p&gt;
&lt;p&gt;Se você já é um profissional da área, tudo que vou dizer é o arroz com feijão, mas estou facilitando seu trabalho: compartilhe com os júniors ao seu redor. E se você é iniciante, é hora de começar a ver o que diferencia sua pequena aplicação das de verdade.&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/KyqFXVVgvIs"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>Por que a aplicação que você tem feito vendo tutoriais e cursos online é BEM diferente do que equipes profissionais realmente fazem de verdade? Por que só &ldquo;estar funcionando&rdquo; não é o suficiente?</p>
<p>Se você já é um profissional da área, tudo que vou dizer é o arroz com feijão,  mas estou facilitando seu trabalho: compartilhe com os júniors ao seu redor. E se você é iniciante, é hora de começar a ver o que diferencia sua pequena aplicação das de verdade.</p>
<p>O episódio de hoje não vai focar em nenhuma linguagem nem framework específicos. Os conceitos de otimização de aplicações web servem pra qualquer um. Cada uma das técnicas é assunto pra um curso inteiro. Tem muitos detalhes, muito caso-a-caso, mas a idéia hoje é dar uma visão geral da importância de não só &ldquo;sair codando&rdquo; e prestar atenção no que está se codando.</p>
<h2>Capítulos<span class="hx:absolute hx:-mt-20" id="capítulos"></span>
    <a href="#cap%c3%adtulos" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li>00:00 - Intro</li>
<li>01:19 - CAP 1 - Recapitulando como Requisição Web Funciona</li>
<li>04:55 - CAP 2 - Multi-processos e Proxy Reverso - Balanceando Carga</li>
<li>11:35 - CAP 3 - Calculando Uso de Recursos - Pool de Conexões</li>
<li>18:00 - CAP 4 - Estratégia de Caching - Economizando Recursos</li>
<li>21:46 - CAP 5 - Bancos de Dados Relacionais, NoSQL, difenças e quando usar - Replicas</li>
<li>28:23 - CAP 6 - Jobs Assíncronos - Devolvendo Rápido</li>
<li>37:05 - CAP 7 - Resumo Até Agora - Diagramas</li>
<li>41:32 - CAP 8 - Calculadoras e Monitores - Dados Analíticos</li>
<li>43:58 - CAP 9 - CDN - Assets mais Rápidos</li>
<li>48:31 - Bloopers</li>
</ul>
<h2>Links:<span class="hx:absolute hx:-mt-20" id="links"></span>
    <a href="#links" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li>Heroku DB Connection Calculator (<a href="https://judoscale.com/heroku-postgresql-connection-calculator/"target="_blank" rel="noopener">https://judoscale.com/heroku-postgresql-connection-calculator/</a>)</li>
</ul>
<h2>SCRIPT<span class="hx:absolute hx:-mt-20" id="script"></span>
    <a href="#script" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Olá pessoal, Fabio Akita</p>
<p>Hoje vai ser um tema bem complicado de acompanhar, mas é pra vocês programadores iniciantes fazendo cursos superficiais ou aprendendo a fazer suas primeiras aplicações Web. O objetivo é tentar explicar qual a diferença do seu app que acabou de conseguir fazer subir na sua máquina depois de ver alguns tutoriais e o que se faz em aplicações de empresas de verdade como Amazon ou Mercado Livre da vida. Por que o ecommerce que você fez passo a passo assistindo um video no YouTube não se parece com nada com uma aplicação de verdade? Qual a diferença? Vamos entender.</p>
<p>(&hellip;)</p>
<p>O que vou mostrar hoje existe detalhado em diversos tutoriais, blog posts, documentação, pra cada framework web. Eu poderia mostrar exatamente qual o jeito que se faz em Ruby on Rails, ou em Spring Boot, mas eu quero que o foco não seja a linguagem ou framework, mas sim os conceitos. Em outro episódio talvez explore um pouco mais no nível do código, mas pra hoje os conceitos são mais importantes do que o código em si, que você pode achar facilmente no Google. Tudo que eu disser aqui é válido pra qualquer framework web. Não existe nada que uma linguagem ou framework faça que outra também não faça. Muita gente pensa que o segredo é escolher a linguagem certa, mas não, é saber como as coisas funcionam além do passo a passo.</p>
<p>Vamos recapitular rapidinho. Você abre seu navegador e chama amazon.com.br. O navegador empacota esse pedido no que chamamos de um HTTP Request, uma estrutura de dados que segue o protocolo HTTP. Esse pacote sai da sua máquina via rede, cai na internet, como expliquei nos episódios sobre redes, e chega até um dos servidores da Amazon. Seja lá no que é feito a aplicação web deles, possivelmente Java, recebe essa requisição. Ele monta a página HTML, empacota num HTTP response, de resposta, e devolve pela mesma conexão de volta pro seu navegador que, finalmente, renderiza a homepage deles pra você ver.</p>
<p>Se abrir o Developer Tools e ir na aba de Rede dá pra ver exatamente o cabeçalho do pacote de requisição aqui, olha só o endereço que pedimos e outros detalhes como que navegador está usando. E em seguida temos o pacote de resposta, que contém um cabeçalho com um código de resposta 200, que significa que deu tudo certo e no corpo vem o HTML.</p>
<p>Se estiver estudando front-end ou mesmo se baixar um template pré-pronto, acho que todo estudante assistindo aqui consegue imaginar fazendo essa página em Laravel, Express, Spring, Django ou Rails. Basta ter um banco de dados qualquer seja em MySQL ou Postgres ou Mongo, montar o HTML e devolver. Um pseudo código seria mais ou menos assim: um template HTML, e uma lógica que faz select na tabela de produtos, pega os 10 ou 20 primeiros, e monta a lista de produtos em HTML.</p>
<p>Qualquer um que tenha assistido qualquer curso online sabe fazer até aqui, certo? Se for uma aplicação em Rails você sobe ela com o comando <code>rails server</code>, se for Django sobe com <code>python manage.py runserver</code>, se for express pode subir com <code>npm run dev</code> ou seja lá como chamou a task. Se for Laravel vai subir com <code>php artisan serve</code>. Se for Spring vai subir com <code>gradlew bootRun</code>. Se for Elixir Phoenix vai subir com <code>mix phx.server</code>. Todo framework tem uma forma de iniciar o servidor web que vai servir a aplicação.</p>
<p>Se funciona assim na sua máquina, provavelmente já imaginou que bastaria então criar um servidor remoto na DigitalOcean, Linode, AWS, Azure, instalar a linguagem, clonar o código fonte do seu projeto e executar o mesmo comando no servidor, e pronto, tá terminado né? Você já é um programador web. Fácil. Basta agora registar um domínio, apontar pra esse servidor e pronto, seu site está no ar. E sim, esse é o jeito errado de fazer. Agora que a coisa começa.</p>
<p>Já notou que quando sobe uma aplicação web na sua máquina, o framework costuma subir numa porta como 3000 ou 4000 ou 8080? Tudo menos porta 80? Mas acho que até o mais iniciante, ou se assistiu meus videos de rede, sabe que o navegador, por padrão, manda requisições HTTP na porta 80 ou 443 dos servidores. Então porque o framework já não deixa sua aplicação rodando na porta 80 direto? Dois motivos. Primeiro porque pra subir na porta 80 precisa ter privilégios de administrador, daí você seria obrigado a subir usando o comando <code>sudo</code> toda hora, o que não é recomendado. Mas em segundo lugar, porque nenhum framework web foi feito pra rodar diretamente exposto na internet na porta 80.</p>
<p>Por exemplo. Tirando Java e Elixir, que são linguagens com suporte a threads reais e multi-thread, todas as linguagens de script, Javascript, PHP, Python e Ruby, são feitos pra rodar primariamente numa thread só. Sim, eles tem um certo suporte a múltiplas threads, mas via de regra, assuma que cada processo que sobe, tem capacidade de saturar somente um dos núcleos da CPU da sua máquina ou do servidor. Pode fazer o teste, faça um código que fica sei lá, fazendo um loop infinito fazendo qualquer coisa, e rode. Agora abra um monitorador de CPU como htop, e vai ver que sua máquina tem várias CPUs, mas a maioria delas não está sendo usada.</p>
<p>Se for instalar num servidor digamos, de 4 núcleos, o certo é subir sua aplicação web 4 vezes, um processo por núcleo. Talvez um pouco menos, depende da carga, já falo disso, mas só um processo vai desperdiçar sua CPU. Um dos processos pode subir na porta 3000, o segundo na porta 3001, a outra na 3002 e assim por diante. Cada processo precisa dar bind numa porta diferente. Mas aí fodeu, como que o navegador do usuário vai saber com que porta conectar? Você nunca teve que digitar amazon.com.br dois pontos 3002, por exemplo.</p>
<p>Como eu disse, nenhum framework web foi feito pra ser exposto direto na internet. Processos web sempre ficam atrás de um balanceador de carga, um outro programa como HAProxy ou, o mais comum hoje em dia, NGINX. Eles fazem um processo chamado Proxy Reverso. Quem fica exposto na porta 80 ou 443 é um NGINX. Os usuários mandam requisições pra esse balanceador de carga que, por sua vez, sabe que tem 4 processos nas portas 3000 a 3003 de pé no servidor por trás e fazem round robin ou alguma outra estratégia de balanceamento pra mandar uma requisição pra cada processo, tentando manter todos igualmente ocupados.</p>
<p>O usuário final não sabe que na primeira vez caiu no processo na porta 3002, quando clicou num link pedindo outra página, caiu na porta 3003 e assim por diante, pra ele é transparente, porque só enxerga o balanceador de cargas. E o processo atrás, também não sabe que existe um balanceador na frente, ele recebe a requisição HTTP como se o usuário tivesse conectado direto nele, como se faz na sua máquina local de desenvolvimento quando pede &ldquo;localhost:3000&rdquo; no navegador. Mas esse é o primeiro jeito mais simples do que chamamos de escalabilidade.</p>
<p>Faz de conta que a lógica que você fez no seu código, pra puxar produtos do banco de dados, montar o HTML e tudo mais leve em média 100 milissegundos. Significa que esse um processo que você subiu na porta 3000 tem capacidade de responder até 10 requisições por segundo, já que um segundo tem mil milissegundos. Se vier mais do que 10 requisições no mesmo segundo, os que vieram depois ficam numa fila esperando o processo desocupar. Por isso subimos mais de um processo na mesma máquina. Se subirmos 4 processos nos servidor, a grosso modo, passamos a suportar 40 requisições por segundo. É isso que chamamos de throughput do servidor, que é a capacidade de quantas requisições ela consegue responder por segundo.</p>
<p>Nenhum programador iniciante pensa nisso, mas o código que você faz tem um limite de quanto consegue responder por período de tempo. Não é mágica. Quanto mais gente for usar sua aplicação, ou subimos mais processos, em mais servidores, ou damos um jeito de fazer a requisição ser respondida mais rápido que 100 milissegundos pra ter mais capacidade no mesmo processo. Por isso que a gente tira tanto sarro de programador copy-e-paste de stackoverflow, porque vocês que fazem isso às cegas, estão adicionando código que diminui a capacidade de resposta de cada processo.</p>
<p>Um proxy reverso como NGINX serve como primeira barreira de segurança. Ele é um software mais simples, melhor otimizado, mais seguro, e que muda muito menos que o servidor web que vem com seu framework, seja o Tomcat de Java ou o Puma de Ruby ou o Cowboy de Elixir. É boa prática esconder eles atrás do NGINX. Mas não só isso, além de servir pra balancear a carga de requisições entre vários processos, é nele que também instalamos coisas como o certificado SSL pra abrir conexões seguras via HTTPS. O navegador fecha uma conexão encriptada com o NGINX, mas atrás dele, o NGINX manda HTTP não criptografado pra sua aplicação. Dessa forma, sua aplicação não precisa se preocupar em lidar com coisas com certificados, o que facilita muito mais o gerenciamento de infraestrutura pros devops.</p>
<p>Sem entrar em detalhes, no mundo Kubernetes esse é o papel do controlador Ingress, que pode usar o NGINX como proxy reverso também. Em resumo, uma aplicação de verdade vai ter NGINX balanceando carga pra múltiplos processos da sua aplicação web, possivelmente em múltiplos servidores. Só que mesmo NGINX também não escala infinitamente. E se meu balanceador de carga estiver no limite?</p>
<p>Tem vários jeitos de resolver isso e um deles é direto no DNS. Quem assistiu meus videos de rede vai lembrar que quando pedimos pro DNS resolver um domínio, o DNS devolve um endereço IP. Esse é o endereço IP pro servidor que tem o NGINX ou equivalente. Mas no caso de serviços muito grandes como Google ou Amazon, eles devolvem múltiplos endereços IPs. E essa é a razão: porque tem vários servidores de balanceamento de carga. Inclusive tem um conjunto de servidores específicos pra cada região geográfica. Se pedirmos amazon.com do Brasil, vai voltar um conjunto de endereços pra servidores daqui. Mas se estiver no Japão, vai receber um conjunto de endereços diferentes pra servidores da Ásia. É assim que uma Amazon da vida consegue escalar em nível mundial.</p>
<p>Lógico, pra maioria de nós, um único servidor de NGINX costuma ser mais que suficiente, mas é bom saber que essas técnicas existem. E aí você pensa. Beleza, entendi. Se eu fiz uma homepage que responde em 100 milissegundos, e eu subir 4 processos num droplet da DigitalOcean, significa que consigo responder até 40 requisições por segundo. Se ver no Analytics que minha campanha de marketing deu certo e o tráfego está subindo pra 80 requisições por segundo, das duas uma, ou eu faço upgrade na máquina pra ter 8 núcleos pra subir o dobro de processos, ou subo um segundo servidor pra subir 4 processos novos. Reconfiguro o NGINX pra saber que existem esses 4 novos processos e ele passa a balancear a carga pra lá também.</p>
<p>Quando fazemos upgrade da mesma máquina, chamamos de &ldquo;escalabilidade vertical&rdquo;, quando colocamos máquinas novas embaixo do balanceador de carga, chamamos de &ldquo;escalabilidade horizontal&rdquo;. E falando desse jeito parece simples. Se o tráfego aumentar pra 400 requisições por segundo, é só subir 10 servidores embaixo do NGINX certo? Só que não, quem dera fosse fácil assim. O que vou falar agora tem muitas nuances por causa do comportamento de múltiplas threads, mas vou tentar simplificar pra ficar fácil de visualizar, só entenda que é mais complicado que isso.</p>
<p>Lembram do video de Heroku? Eu ainda recomendo como melhor lugar pra iniciar, mesmo tendo cancelado os planos gratuitos. Pra só brincar, precisa achar outro lugar, mas se pretende colocar um ecommerce no ar e queria colocar num plano gratuito, você precisa repensar seu negócio. Uma aplicação que sequer consegue pagar a própria infraestrutura, tem problemas muito mais sérios do que custo de máquina. Uma das vantagens é que essa parte toda de balanceador de carga NGINX é transparente. Eles gerenciam, você só precisa mandar subir os processos no servidores deles, que eles chamam de dynos. E o balanceador é automaticamente reconfigurado.</p>
<p>Tem uma calculadora online que vai nos ajudar a ilustrar o que eu quero dizer aqui. Olha só. Digamos que estamos naquele cenários de 400 requisições por segundo. Num dyno pequeno de 4 cores, a recomendação é subir 3 processos. Se pegar um dyno maior o recomendado é subir o total de cores menos um. Lembra que o sistema operacional também precisa de um pouco de CPU pra gerenciar o que roda por cima, por isso não queremos saturar tudo só com nossa aplicação. Você tem que saber pensar essas coisas. Então vamos subir 3 processos, só que aí, pra responder 400 requisições por segundo, 10 requisições por processo, dividido por 3 processos, vamos precisar de pelo menos 13 dynos.</p>
<p>Eu disse que linguagens de scripts lidam com múltiplas threads diferente de Java ou Elixir. Eles tem threads, só não conseguem rodar todas em paralelo o tempo todo. Eu falo sobre isso no episódio sobre concorrência e paralelismo. Mas na prática podemos configurar cada processo pra aceitar, digamos, 5 threads, ou seja, 5 conexões simultâneas. Não pensem muito sobre isso agora. Mas o importante é o número a seguir: 5 conexões a banco de dados no pool de conexões. O que diabos é um pool de conexões?</p>
<p>Conexão a banco de dados é uma operação que custa recursos do banco de dados. Não dá pra ficar abrindo conexão infinitamente. Estão notando um tema no video de hoje: nada é infinito em programação. Você como iniciante só não sabe disso porque só testando seu programinha do tutorial sozinho é muito pouco pra chegar no limite. Mas na internet, com milhares de usuários de verdade usando, toda hora vai esbarrar em limites e sua função, como programador, é saber como gerenciar esses limites. Pensa assim, cada sessão de uma conexão no banco de dados precisa ser registrada em algum lugar. Esse registro vai gastar memória e CPU. Quanto mais conexões, mais memória e CPU vai se gastar pra gerenciar todo mundo.</p>
<p>Seja o processo da sua aplicação web ou o processo do banco de dados, sem um limite, uma de duas coisas vai acontecer: ou vai acabar memória, ou vai acabar processamento. Mas um dos dois vai acabar. Quando isso aconteder, tudo trava e pára de responder. Não queremos que isso aconteça, então configuramos limites, pra conseguirmos pelo menos ter processamento pra rejeitar e devolver erro caso um dos limites seja atingido. Antigamente a gente deixava rodando até passar do limite e era o caso onde páginas na web ficavam com ampulheta de &ldquo;carregando&rdquo; por minutos, sem resposta. Hoje tomamos um timeout pelo menos.</p>
<p>Uma das técnicas que adotamos lá na virada do século foi pool de conexões. Digamos que minha aplicação Java mal feita saísse abrindo conexões e esquecendo de fechar elas. Rapidamente o limite do banco ia estourar. Se o mesmo banco fosse usado por várias aplicações diferentes, bastava uma mal feita, pra saturar todas as conexões. Em vez disso limitamos a quantidade que cada aplicação pode usar. A aplicação deixa de conectar direto no banco, em vez disso ela se conecta num pool, e esse pool mantém um certo número fixo de conexões abertas.</p>
<p>No exemplo da calculadora, cada processo da sua aplicação pode ser configurado com 5 conexões. Se tiver 3 processos no mesmo dyno, cada dyno vai consumir 15 conexões com o banco. Se o banco suportar até 150 conexões, dá pra subir até 10 dynos, em teoria. E isso deveria ser suficiente. Se cada processo aceita até 5 threads simultâneas, e cada thread precisar de uma conexão, vai ter pelo menos 1 pra cada. Quando a thread terminar de usar a conexão, ela devolve pro pool, pra poder ser reusada por outra thread.</p>
<p>O pool faz meio que o papel do NGINX entre o navegador do usuário e sua aplicação, é um intermediário de balanceamento. No caso, a função do pool de conexões é permitir múltiplas threads da aplicação reusar conexões já existentes em vez de toda hora tentar abrir novas conexões. E com isso conseguimos controlar a quantidade máxima de conexões sendo utilizadas ao mesmo tempo.</p>
<p>Serviços como o Heroku Posgres ou AWS RDS tem diversos planos de tamanhos e preços diferentes. Um iniciante pensa num plano de banco de dados mais como dólares por capacidade de armazenamento de dados. Sei lá, 10 dólares pra 1 terabyte, 100 dólares pra 10 terabytes ou algo assim. Mas mais importante que total de armazenamento é qual o máximo de conexões que cada plano oferece. Um plano básico de Heroku Postgres, pode oferecer, digamos 120 conexões no máximo.</p>
<p>Ou seja, de acordo com a simulação que fizemos na calculadora. Subindo minha aplicação em 13 dynos, com 5 threads cada, com um pool de 5 conexões, 13 vezes 5 vezes 5, significa que preciso de no mínimo 195 conexões pro banco de dados. Ou seja, um plano básico de 120 conexões máximas, já não vai dar. Se realmente tiver as tais 400 requisições por segundo, das duas uma, ou eu pago mais e aumento meu plano de Postgres, ou uso um paliativo que é colocar um gerenciador de conexões como o pgbouncer na frente do Postgres.</p>
<p>A grosso modo funciona assim. Antes, na sua máquina, sua aplicação conectava direto no banco de dados. Não fazemos mais isso, todo framework decente já trás um pool de conexões pra você usar e é ele quem se conecta no banco. Mas podemos colocar uma camada a mais e o pool também não fala mais direto com o banco e sim com outro intermediário, o pgbouncer. O pgbouncer vai usar no máximo uns 75% do máximo de conexões do banco, nesse exemplo seriam 90 conexões. Mas ele tem capacidade de deixar até 10 mil pedidos de conexão pendurados em cima dele antes de dar erro.</p>
<p>Significa que não vamos tomar erro de falta de conexão, mas as requisições precisam esperar mais tempo e se ficar pesado demais, tomar timeout de qualquer jeito, só estamos tentando esticar um pouco mais. É como se fosse a fila de um Poupatempo ou cartório da vida. Só tem 90 atendentes, mas tem 195 pessoas na fila. As primeiras 90 são atendidas imediatamente, o pgbouncer são as cadeiras de espera pras outras 105 ficarem esperando com a senha pra serem atendidos. A solução de todos os limites em programação é a mesma de uma repartição pública: fazer fila e esperar a vez.</p>
<p>Só que isso é um paliativo. Significa que se antes cada requisição era respondida em até uns 100 milissegundos, agora pode levar mais tempo, talvez 120 milissegundos. Isso muda a conta toda que fizemos até agora. Se o número de processos por dyno é fixo, precisamos subir mais dynos. Mas se subirmos mais dynos e carregarmos mais processos, isso vai pressionar mais o pool de conexões, que vai exigir mais conexões ao banco de dados. Estão ententendo como as coisas não são fáceis?</p>
<p>Existem várias formas de resolver isso. Chegamos ao limite do que só configurando infraestrutura pode fazer. Será que não dá pra resolver isso no código? Opa, talvez sim. Dos 100 milissegundos que sua aplicação web leva pra montar a homepage, 50 é gasto só mandando a query de select pro banco, recebendo as linhas de resposta e processando esses dados. Mas pensa comigo, no seu ecommerce pequeno, literalmente todo mundo que pedir a homepage vai acabar vendo os mesmos produtos, não vai? Se vieram 400 requisições de homepage, você fez 400 pesquisas no banco de dados que ficou respondendo 400 vezes a mesma coisa. Isso é um puta desperdício.</p>
<p>Todo framework web que se preza já tem embutido recursos pra fazer cache. Cache é guardar a última resposta pra não reprocessar tudo de novo a mesma coisa. Se fizermos a implementação mais simples e tosca, fazemos um código pro seguinte: procure por um arquivo local chamado &ldquo;cache-homepage&rdquo;. Se não tiver, faça a mesma coisa de antes: solicite uma conexão ao banco pro pool, mande o select pra fazer a pesquisa, receba as linhas de resposta, processe e monte o HTML baseado nos produtos recebidos. Mas agora, antes de devolver pro usuário, grave um arquivo local chamado &ldquo;cache-homepage&rdquo;. Pronto.</p>
<p>Na segunda requisição por homepage que vier, agora ele vai encontrar esse arquivo, podemos checar que esse arquivo não é mais velho que 5 minutos, faz de conta, e pulamos toda a parte de falar com o banco de dados, devolvendo direto o que estiver nesse cache. Entenderam? Vamos fazer as contas. Digamos que subimos só 8 dynos, com 3 processos cadas, 5 threads e 5 conexões no pool, o que vai consumir todas as 120 conexões que o banco Postgres que contratei suporta no máximo desse plano. Daí começa a vir requisições. Digamos que o NGINX balanceou perfeitamente e as primeiras 8 requisições que chegaram mandou um pra cada um dos 8 dynos.</p>
<p>Em cada dyno, o processo que respondeu primeiro fez conexão com o banco e deixam esse arquivo &ldquo;cache-homepage&rdquo; pra trás. Agora chegou o resto das 392 requisições durante 1 segundo. Sabe quantas conexões pro banco foram abertas nesse segundo? Antes precisava de 195. Agora precisou de zero. Sacaram? Zero! Porque toda requisição que veio depois que o arquivo de cache foi gravado, leram direto desse arquivo e não precisaran conectar nem no pool, nem no banco. E se arbitrariamente estabelecermos que a lista de produtos não corre risco de sofrer alterações durante 5 minutos, por 5 minutos, essa homepage passa a consumir zero conexões com o banco de dados.</p>
<p>Esse é o tipo de otimização simples com alto impacto. Antes precisávamos de 195 conexões com o banco por segundo, agora precisamos de não mais que uns 10 a cada 5 minutos. E como 50 milissegundos por requisição era gasto só nesse trabalho com o banco, significa que na média eu diminuí o tempo de cada requisição pela metade! Sem trocar linguagem, sem trocar framework, sem reescrever a aplicação. Eu poderia diminuir pela metade a quantidade de dynos pra responder as mesmas 400 requisições por segundo. Ou deixo como está e posso fazer mais marketing pra dobrar o tráfego e ainda consigo responder todo mundo com folga.</p>
<p>Vou insistir no conceito pra martelar na cabeça de vocês. Trocar de linguagem ou framework, sair de Python e ir pra Javascript, ou sair de Dotnet e ir pra Java, sair de Laravel e ir pra Express, ou qualquer coisa assim, pode dar alguma diferença pequena em algumas partes. 5%, 10%. Mas técnicas como essa de cache, colocadas em lugares inteligentes de alto tráfego, podem economizar recursos em ordens de grandeza, 5x, 10x, ou até mais. É esse o tipo de otimização que vale a pena fazer. Gastar semanas, pra ter ganho de 1%, 2%, não vale a pena. Gastar horas pra ganhar 5x, 10x, isso sim vale a pena.</p>
<p>Eu falei arquivo só pra explicar o conceito mais fácil, mas na realidade não usamos arquivos pra cache. Usamos um segundo banco de dados mais leve, como um Memcache ou Redis. São bancos que não tem as garantias de um Postgres de atomicidade, consistência, integridade ou durabilidade, ou seja, pode ser que eu mande gravar e ele não grave um registro, pode ser que grave mas quando for ler não está lá ainda, pode ser que grave duas vezes.</p>
<p>Se fosse um banco de dados de pagamento isso seria um problema. Mas pra cache não. Não tem problema, porque a lógica da aplicação que fizemos é: se existir no cache e não expirou, usa, se não existir, conecta no banco, remonta o HTML e grava um novo cache.</p>
<p>Não ter essas garantias significa que o Memcache ou Redis conseguem executar muito mais rápido e aceitam uma ordem de grandeza mais conexões simultâneas. Por isso configuramos um Memcache, que é um serviço barato, e fazemos essa lógica de cache pra ler e gravar do memcache em vez de um arquivo. A desvantagem é que precisamos pagar mais um serviço. Mas ele é barato e economiza mais ainda os recursos do banco, porque antes cada dyno precisava gerar seu próprio arquivo de cache, então quanto mais dynos, mais continua precisando puxar do banco na primeira vez. Agora só precisa uma vez e todos os dynos lêem do mesmo memcache.</p>
<p>Memcache como o próprio nome diz, é Memory Cache. É literalmente um cache em memória. Por isso que quando o servidor de memcache reinicia, perde tudo que tinha armazenado. Mas não tem problema, porque toda aplicação que depende do cache sabe como reaquecer esse cache, ou seja, reconstruir os dados que precisa em cache pra próxima requisição. Um Redis é parecido, mas com a vantagem que também grava em disco, daí se reiniciar, ele sabe reaquecer sozinho. Ele é pouca coisa mais lento mas tem durabilidade. De novo, é um trade-off. Em ambos os casos, significa que não precisamos aumentar o plano de banco de dados.</p>
<p>Mas a coisa não termina aqui. Isso foi só Hello World do Hello World. Bancos de dados é outro assunto infinito que daria pra fazer um canal inteiro só sobre esse assunto, mas o importante é que todo banco de dados tem perfis de performance de escrita e de leitura. Um banco de dados relacional como MySQL, Postgres, SQL Server, Oracle, costuma ter perfil de leitura rápida mas escrita lenta, justamente porque tem como característica as tais garantias ACID que falei antes. É lento pra escrever, porque se ele diz que escreveu, é porque escreveu e escreveu garantidamente correto. Ninguém vai receber um pedido duplicado, ou ser cobrado duas vezes, ou deixar de receber porque deu erro na gravação e ninguém ficou sabendo.</p>
<p>Os tais bancos NoSQL, tem outras características. Um Redis, Mongo, Cassandra, te dão menos garantias, por isso podem ser mais rápidos pra escrever. Eu já fiz um video sobre NoSQL, depois assistam. Mas quando faz sentido ser rápido mas não ter garantias? Pensa um tipo de aplicação que se perder um registro não faz diferença. Por exemplo, um Google Analytics. Todo site que tem configurado vai mandando pro Analytics cada clique e cada visita dos usuários no site. Se uma dessas visitas se perder e não registrar no Analytics, não vai fazer diferença. Ou seu Apple Watch, que fica monitorando batimentos cardíacos a cada X segundos. Ele manda pro servidor pra ir montando um perfil da sua saúde. Se um desses batimentos se perder e não gravar, não importa, o importante é a média ao longo do tempo. São casos onde não ter as garantias ACID permite usar um outro banco mais rápido.</p>
<p>Um MongoDB pode ser quase ACID se configurar  pra esperar pra dar ok na escrita, pra garantir que realmente gravou. Só que aí perdemos a característica de ter escrita mais rápida e vai ter que esperar de qualquer jeito. Um Cassandra tem escrita atômica, mas consistência eventual. Ou seja, até garante que escreveu, mas se consultarmos imediatamente talvezresponda que ainda não está lá, porque a leitura caiu num outro node que ainda não sincronizou. Isso é útil quando o que queremos é replicação do banco em múltiplos servidores.</p>
<p>Não existe um tamanho que serve tudo. Por isso que pra decidir qual banco usar, precisa entender a diferença nesses perfis de todos em detalhes. Não vai ter uma resposta fácil pra tudo. Mas no geral, pra grande maioria da população que não é uma startup unicórnio, você realmente só precisa de um banco de dados relacional como Postgres sendo auxiliado por um servidor de cache como Memcache ou Redis. Mais do que isso e não é vendo um video que vai ajudar.</p>
<p>E mesmo quando falamos leitura. Existem leituras simples, como puxar uma lista de produtos pra montar uma página, e leituras complicadas que é gerar um relatório das vendas do semestre organizado por região e tudo mais, que cruza os dados de todos os pedidos feitos no semestre, com os dados de todos os clientes, e seja lá quantas outras tabelas pa montar um único relatóriozão. Quem entende de SQL é aquela query com 500 joins, outer joins, sub-selects. Se já trabalhou em algo assim, já viu esse problema: quando seu ecommerce ou sistema fica lento ou até pára de responder toda vez que precisa gerar esses relatórios gigantes.</p>
<p>Uma coisa é uma pesquisa simples que responde em poucos milissegundos. Outra coisa são relatórios que podem levar múltiplos segundos travando o banco inteiro. E se você tiver um tráfego de 400 requisições por segundo, de repente, todo mundo tendo que esperar na fila até o relatório terminar, e levar 10 segundos pra terminar, são 4000 requisições que estamos deixando de responder, centenas de pessoas tomando timeout no navegador e ficando irritados que o site não responde.</p>
<p>Por isso que num sistema de verdade, dividimos essas coisas em múltiplos servidores. Temos o banco principal, que está ocupado gravando pedidos de pessoas de verdade e não pode ser interrompido quando algum gerente pede um relatório gigante. Por isso que em serviços como Heroku Postgres ou AWS RDS tem fácil pra se gerar servidores de réplica só pra leitura. É uma cópia que pode estar até alguns segundos atrasado do banco principal e que não deixa ninguém gravar nele, mas como fica num servidor separado do banco principal, mesmo se rodarmos coisas pesadas nele, não interrompemos o tráfego do site, que conecta em outro servidor.</p>
<p>Na verdade é bem comum ter um banco principal só pra escrita e uma réplica só pra servir dados pra montar as páginas do site, e outra réplica exclusiva pra tarefas administrativas como gerar relatórios. Quem fala que quer usar um NoSQL porque bancos relacionais não escalam, não sabem o que estão falando. Em escala de empresas normais não-unicórnios, não precisa. De novo, soluções exotéricas de NoSQL só são necessários se você for o Netflix. Mas pros resto da população, um banco de dados tradicional relacional com réplicas de leitura, já resolve a grande maioria dos problemas. Lembra? Soluções simples de alto impacto que não exige reescrever tudo. É isso que sempre queremos.</p>
<p>Mas ainda temos um último problema de banco de dados. Até que não é tão difícil ter réplicas só de leitura. E eu digo só de leitura porque ter replicação bi-direcional de leitura e escrita, é pouco prático e só deve ser considerado em situações especiais. No geral é melhor ter um único servidor que concentra todas as escritas e envia os dados pra réplicas só de leitura. Mas isso significa que em algum momento, se o tráfego do site aumentar, numa situação de semana de Natal, Black Friday da vida, vai ter mais gente tentando gravar pedidos do que o servidor principal consegue suportar.</p>
<p>Uma lógica comum numa página de checkout de ecommerce é mais ou menos assim: a lógica do carrinho de compras já checou que tem disponibilidade dos produtos, seu front-end já coletou os dados de entrega, os dados de pagamento, e agora iniciamos a transação pra gravar o pedido. Dentro da lógica dessa transação, conectamos com o meio de pagamento, seja MercadoPago, Paypal, ou o que for, e esperamos responder ok. Se foi confirmado, terminamos a transação e fechamos o commit na tabela do banco de dados e, finalmente, respondemos o HTML pro usuário com o número do pedido que acabamos de gerar. Durante esse período todo o navegador do usuário ficou pendurado, com ampulhetinha rodando, esperando carregar.</p>
<p>Num dia normal de pouco movimento, ou na sua máquina de desenvolvimento, isso funciona perfeitinho. Mais um exemplo de coisa que parece simples só porque você não tem noção da realidade. Mas agora imagina. Você não controla coisas externas como o meio de pagamento. E num dia de Black Friday, ele também pode ficar mais lento. Num dia anormal, tem um monte de gente pendurado nos seus servidores, todo mundo tentando dar checkout nos pedidos. O banco de dados tem limite de conexões, tem limite de transações, e chega uma hora que mesmo tendo fila, o tempo é tão longo que ele é obrigado a começar a desconectar as pessoas pra conseguir trabalhar. É quando o cartório tá tão cheio que não cabe mais gente dentro. O que você faz, manda todo mundo embora?</p>
<p>Parte disso já expliquei no video sobre o Ingresso.com. Onde resolvemos a primeira parte do problema, na parte da frente da loja, que é dar senha pras pessoas e manter uma fila muito mais longa do tipo &ldquo;volta mais tarde pra ver se já chegou sua vez&rdquo;. E pelo menos o navegador dos usuários não ficam ativamente pendurados nos servidores web, consumindo recursos. Eles podem desconectar, e uma lógica no navegador conecta de tempos em tempos pra checar ou usa protocolos apropriados pra isso como Web Sockets. Mas e depois que as pessoas estão na loja e querem pagar o pedido?</p>
<p>E, pra surpresa de ninguém, não é diferente, a solução é fazer mais filas. Muita gente associa o número do pedido com o ID da gravação da linha na tabela que o banco de dados auto-incrementa e devolve quando deu tudo certo na gravação. Mas se já estamos no cenário onde passamos do limite físico de gravações por segundo que o banco aguenta, não podemos depender disso. O correto é ter uma API ou um microserviço cuja única função é me dar um número que é garantidamente único e não duplicado. Tem várias formas de fazer isso. Mas só entenda o conceito que desassociamos o número do pedido do ID da tabela de pedidos.</p>
<p>Em seguida, não gravamos mais o pedido direto no banco. Em vez disso pegamos os dados do pedido, com esse número e jogamos num serviço de fila. Já falo disso, mas ao fazer isso, quando o usuário preenche seus dados no formulário de checkout e clica em pagar, imediatamente devolvemos a página HTML de resposta dizendo &ldquo;obrigado, o número do seu pedido é XYZ, e assim que confirmarmos você receberá um e-mail&rdquo;. E pronto, liberamos essa pessoa pra ir embora, e ela pára de consumir recursos no nosso sistema tendo que ficar esperando. É o equivalente de fazer a pessoa ir embora do cartório ou correios e dar espaço pra outra pessoa entrar logo, fazendo a fila andar mais rápido.</p>
<p>Existem vários serviços de fila. Podemos usar o próprio Redis que usamos pra cache pra ser o gerenciador da fila. Podemos usar um serviço feito exclusivamente pra filas como o RabbitMQ ou Apache Kafka ou ActiveMQ ou vários desses MQ que significa Message Queue ou Message Brokers. A AWS oferece o ActiveMQ ou RabbitMQ como serviço ou podemos usar o pŕoprio dele que é o AWS SQS de Simple Queue Service. Todos são Queues, ou filas. A vantagem de serviços de fila é que podemos ficar enviando milhares de mensagens pra eles enfileirarem, que vai ser muito mais rápido e eficiente do que gravar no nosso banco de dados que já tá no limite de processamento.</p>
<p>Eu falei Redis, ele funciona direitinho, mas não é tecnicamente feito pra ser uma fila de verdade. Protocolos de fila de verdade, como implementado num RabbitMQ tem garantias como um banco de dados relacional tem ACID. Um serviço de fila de verdade precisa nos dar garantia de entrega, ou seja, se ele responde que entregou, precisa ter entregue de verdade, e não pode duplicar a mensagem ou coisa assim. Além disso um erro que alguns cometem é criar uma tabela no banco de dados pra usar como fila. Mas se estamos querendo justamente tirar pressão do banco de dados usando uma fila, não faz nenhum sentido botar a fila no banco.</p>
<p>Uma vez na fila, daí nós programadores precisamos fazer uma segunda aplicação separada, que chamamos de workers. Workers são programinhas curtos que ficam de pé esperando alguma coisa aparecer numa fila dessas. Eles ficam escutando essas filas, normalmente falamos que assinam um canal dessas filas. Uma fila pode ter um canal de recebimento de pedidos, outro canal de envio de emails, canal de realizar pagamentos. Cada canal vai ter um worker que sabe o que fazer com a mensagem que tiver naquele canal. O Worker que assina o canal de realizar pagamento sabe como se conectar com o meio de pagamento.</p>
<p>A vantagem é que separamos as atividades que não controlamos, como confirmação de pagamento, e não precisamos deixar o usuário pendurado esperando uma resposta. Mandamos o usuário embora o quanto antes, e mesmo que o meio de pagamento resolva demorar bem mais que o normal, tudo bem, só vamos demorar um pouco mais pra mandar o email de confirmação pro usuário. E não precisamos torturar o nosso banco de dados com várias transações abertas penduradas que começam a dar timeout e rollback sem parar.</p>
<p>De novo, cada framework web moderno tem sua forma de lidar com isso que chamamos de asynchronous jobs. No Ruby on Rails tem ActiveJob, que é uma classe que herda de ApplicationJob e implementa um método chamado <code>perform</code>. Na configuração geral dizemos que vai escutar de um Redis ou outro serviço de fila. No Node Express podemos usar a biblioteca Bull, onde configuramos um objeto de Queue que também diz de que fila consumir e os workers, que ele chama de consumers, onde temos uma função chamada <code>process</code> pra processar cada job que vier da fila.</p>
<p>Em Spring podemos integrar com Apache Kafka, fazendo uma classe consumer com um método <code>consume</code>. Em PHP o Laravel tem suporte nativo, onde fazemos uma classe que herda de ShouldQueue e implementa um método <code>handle</code>. Acho que deu pra entender. E cada um tem estratégias de como configurar esses workers, como fazer deploy e tudo mais. Vocês precisam estudar a documentação oficial. Em qualquer outro framework, pesquise no google por &ldquo;asynchronous jobs&rdquo;.</p>
<p>Mas claro, nem tudo são flores. Você poderia pensar. &ldquo;Boa, então é só eu pendurar 500 workers atrás dessas filas que tudo vai ser processado rapidinho então&rdquo;. Mas não. Alguns desses workers, no final, continuam precisando gravar coisas como o pedido na tabela do banco de dados principal. Eles podem demorar mais do que a aplicação web, porque não tem um usuário pendurado esperando, mas o pedido final precisa ser consolidado no banco e aí caímos de novo no problema de máximo de conexões e processamento do servidor de banco.</p>
<p>Se voltarmos naquela calculadora do Heroku, note que embaixo tem uma parte que eu não tinha mostrado. O Heroku suporta subir workers, mas cada worker vai consumir conexões no banco também. Se subir 10 dynos, com 1 processo cada, 10 threads por processo e 10 conexões no pool, vai precisar de 100 conexões no banco de dados, além das conexões que já precisava pros dynos de web. Por isso, de novo, não é mágica. Temos que ficar balanceando quantos processos web versus quantos processos workers queremos ter pra um certo orçamento que estou disposto a pagar pela infraestrutura.</p>
<p>Sacaram? Organizar esses recursos é parecido com jogar RPG, quantos pontos queremos de vida, quantos pontos queremos de mágica, quanto quero gastar em upgrade  de arma. Não adianta ter a melhor arma, mas pouca vida. Configurar arquitetura web é que nem jogar RPG.</p>
<p>Mas como começamos a colocar cache na camada web, o que diminui a necessidade de dynos web, sobra mais conexões pra colocarmos mais workers. E é assim que vamos evoluindo de uma aplicação de um único processo que roda na sua máquina pra uma que divide os recursos e nos dá oportunidades de balancear onde queremos usar mais do que.</p>
<p>Vamos ver o que acabei de falar num diagrama pra facilitar. Começamos assim: uma caixinha que representa um processo da nossa aplicação web, aquela que você sobe na sua máquina com comandos como <code>npm run dev</code> ou <code>gradlew bootRun</code>. Esse processo vai configurado pra conectar num banco de dados como Postgres. Seu navegador se conecta direto nessa caixinha, via <code>localhost:3000</code>. E na sua máquina tudo funciona.</p>
<p>Mas numa infraestrutura de verdade, como AWS, Azure, Google Cloud ou Heroku que gosto de usar de exemplo pela simplicidade, já começa que na frente da caixinha da sua aplicação vai ter um balanceador de carga, como um NGINX. É nele que seu navegador vai se conectar quando digitar o domínio da sua aplicação. É o endereço IP dele que você configura no registro do seu domínio pra DNS. No painel de todos  tem como configurar certificados SSL e tudo mais. Ele que vai pegar o tráfego dos usuários e direcionar pra containers web, os dynos. DigitalOcean chama de droplets. Em Kubernetes chamamos de pods. Mas enfim, é onde roda o processo da sua aplicação.</p>
<p>Na realidade, em produção não vai ter só uma caixinha da sua aplicação. Vai ter vários. Cada um numa porta separada no tal container ou dyno. E o balanceador de carga vai distribuindo uma requisição pra cada uma delas, tentando manter todos ocupados por igual, se possível. Agora, todas essas caixinhas continuam precisando se conectar no banco de dados. Mas já sabemos que existe um limite máximo dependendo do plano que pagamos. Pra não perder controle, cada caixinha vai ter um pool de conexões, com um máximo de conexões reusáveis que ele pode manter.</p>
<p>Pra não desperdiçar recursos do banco de dados, a técnica mais importante é estrategicamente colocar cache nos lugares ou mais pesados ou que são acessados com mais frequência e cujos dados não mudem muito, ou que pelo menos não mude muito no espaço de alguns minutos que seja. 5 minutos que suas caixinhas não se conectam no banco faz muita diferença. Lembrem-se, temos muitas caixinhas ativas ao mesmo tempo! Então subimos um novo serviço, um Memcache. Um serviço de cache também tem um limite de quantos megabytes ou gigabytes de RAM queremos usar de cache.</p>
<p>E não precisamos nos preocupar se encher esse espaço porque o Memcache é inteligente de ir apagando cache mais velho pra dar espaço pra cache mais novo. E eu só dei exemplo de cachear algo que todo mundo vê igual, como lista de produtos numa homepage, mas podemos fazer cache por usuário. Assim se o usuário ficar dando refresh na página de perfil dele, não precisa ficar toda hora indo no banco pra pegar os mesmos dados. Vai estar no cache dele. Estratégias de cache são importantíssimos, vale estudar em detalhes.</p>
<p>Mas à medida que nossa aplicação cresce, temos funcionalidades pesadas, integrações com serviços de terceiros, como meios de pagamento, sistemas de gestão como um ERP, várias coisas que aumentam o tempo de resposta e fazem nossos usuários esperar tempo indeterminado. E nosso banco de dados também não tem processamento infinito. Mesmo colocando cache, ainda assim precisamos controlar esses recursos.</p>
<p>Pra isso podemos começar distribuindo a leitura em servidores de réplica desse banco. Todo bom framework tem como configurar pra toda leitura ser feita num banco de dados e toda escrita ser feita em outro banco de dados. E esse banco de escrita é responsável por atualizar as réplicas. Mesmo se as réplicas ficarem um pouco atrasadas, alguns segundos, pra coisas como relatório semestral de vendas ou algo assim, não vai fazer diferença. E pra coisas como confirmação de número de pedido, podemos até deixar no cache do usuário, enquanto os bancos de dados ainda não se sincronizaram. Assim ninguém vai notar a diferença de tempo.</p>
<p>Mas só isso não é suficiente. O mundo ideal é que toda requisição HTTP feita por um usuário seja devolvido imediatamente, sem precisar esperar ninguém. Por isso não chamamos serviços externos como pagamento ou outras APIs no mesmo processamento de requisição HTTP do usuário. Devolvemos imediatamente pra ele um &ldquo;aguarde, depois mandamos um email confirmando&rdquo;. E criamos uma tarefa assíncrona que jogamos numa fila como SQS, Kafka ou mesmo Redis, então instalamos uma nova caixinha aqui que vai ser responsável só por isso.</p>
<p>E por fim, criamos workers ou consumers pra essas filas, pra fazer o processamento a posteriori, realizar pagamento, integrar com ERP, integrar com nota fiscal eletrônica, envio de e-mails e tudo mais, e só aí confirmamos gravando no banco de dados principal. Como essas tarefas podem demorar um pouco mais, também não saturamos o banco de dados principal e tudo continua funcionando de forma mais estável.</p>
<p>Mas não é só ir subindo caixinhas aleatoriamente. Pra isso é bom ter em mente aquela calculadora que mostrei antes. Existem várias calculadoras assim pra coisas como AWS, só procurar. Mas não espere que alguém te dê uma resposta exata. Tempo médio de uma requisição não o único número que você precisa. Numa aplicação web, páginas de conteúdo como lista de produtos, detalhes de produtos, perfil do usuário, são coisas facilmente cacheáveis e que respondem rápido.</p>
<p>Mas páginas de gerenciamento de carrinho que checa disponibilidade de produtos, checkout com integração de pagamentos, são páginas que demoram muito mais. Chutando, numa página de conteúdo conseguimos responder em menos de 50 milissegundos, mas uma página mais complicada de checkout poderia levar 500 milissegundos. São diferenças assim de 10x que se encontra em diversas partes de uma aplicação Web.</p>
<p>Se tirar a média, vamos chegar num número tipo 100 milissegundos, que não serve pra muita coisa, porque estamos escondendo as páginas pesadas que precisaríamos otimizar, e talvez estejamos otimizando páginas de conteúdo que já são rápidas o suficiente. Por isso eu sempre recomendo que se instale uma ferramenta online de monitoramento. A melhor que conheço é o produto RPM da New Relic.</p>
<p>Tem boas alternativas como o Scout e outros que não conheço. Mas o New Relic dá um raio X mais detalhado. Ele mostra quem são os principais ofensores, e se ele suportar o framework web que você usa, como Ruby on Rails, é capaz de dizer qual controller, qual model, qual query que realmente tá pesada. Assim não precisamos ficar adivinhando.</p>
<p>Otimização nunca deve ser feita no chutômetro. &ldquo;Ah, eu acho que é essa classe, ah eu acho que se refatorar esse job fica melhor&rdquo;. Olhem os números. 80% dos problemas de performance costumam ser causados por 20% da sua aplicação. Não precisa reescrever tudo. Em 10 funcionalidades, deve ter 2 que precisamos focar e conseguir economizar metade dos recursos da infraestrutura inteira. Como no exemplo do cache que mostrei no começo.</p>
<p>Toda otimização deve ser baseada em dados analíticos de uso real em produção, dados de uso de usuários de verdade usando sua aplicação. Por isso precisa de um New Relic. Uma vez estabelecido quem é o ofensor, sabemos quanto tempo tá consumindo. E quando fizer a correção e subir em produção, basta medir algumas horas e dá pra saber quanto foi efetivo ou não. Por isso não basta funcionar só na sua máquina, testando só com um usuário. Assuma que você não sabe nada até medir em produção.</p>
<p>Pro pessoal de front tem uma última otimização importante: aprender a usar CDN. O problema é o seguinte. Quando se monta o HTML e o servidor devolve pro navegador, esse HTML vai conter várias tags de imagens, css, scripts. Agora o navegador precisa voltar pro servidor e ir pedindo todos esses assets. Então uma requisição se multiplica em 20, 50, sejá lá quão complexo é o HTML que você fez. Normalmente um servidor web é bem rápido pra devolver essas coisas, já que não envolve conectar com banco de dados, APIs nem nada disso, mas mesmo assim, em grande volume, também pesa. Aquelas caixinhas da sua aplicação tão perdendo tempo devolvendo imagens sendo que podiam estar usando esse tempo pra gravar mais um pedido da sua loja.</p>
<p>Cada framework web, de novo, tem formas de integrar com um CDN como o AWS CloudFront ou Azure CDN ou Fastly. CDN funciona assim: em vez de escrever uma tag de imagem com o source relativo ao seu domínio, explicitamente escrevemos a URL do serviço de CDN tipo &ldquo;minhalojablabla.cloudfront.com&rdquo; ou seja lá qual URL que o serviço te der. E reescrevemos a tag de imagem usando um source absoluto. Só que isso seria um saco por dois motivos. Enquanto se está desenvolvendo a aplicação, nem tem o CDN configurado ainda, daí você,  desenvolvedor front-end, nem ia conseguir visualizar o que tá fazendo. O segundo problema é se escrever na mão as URLs pro CloudFront mas aí o pessoal de infra decide que prefere mudar pro Azure. E aí? Sai mudando todas as URLs na mão?</p>
<p>Lógico que não. Pra isso usamos helpers. Funções que escrevem a URL pra você. Em vez de escrever URL relativa ou absoluta pro CDN, use a linguagem de templates do seu framework e chame funções. Por exemplo, no Node Express, usando EJS, poderia escrever a tag de imagens assim como nesse exemplo. Essa função vai checar se está rodando na sua máquina local, daí escreve a URL relativa normal. E quando estiver em produção, fazemos uma configuração que indica pra função qual é a URL do CDN e ele reescreve usando URL absoluta.</p>
<p>Esse exemplo é usando express-cdn mas tem várias bibliotecas que fazem a mesma coisa. É importante que todo desenvolvedor de front saiba fazer isso pra depois não ter que ficar caçando URL hard-coded pra sair mudando em massa um dia antes do primeiro deploy. A vantagem de fazer isso é que a partir de agora, se feito direito, sua aplicação web devolve o HTML pro navegador. O navegador encontra essa tag de imagem apontando pro servidor de CDN e vai pedir pra lá. O servidor de CDN vai checar &ldquo;hum, eu tenho essa imagem? Não&rdquo;, daí ele vai pedir só uma vez pra sua aplicação web e vai cachear a imagem de resposta. Todo mundo que pedir a mesma imagem vai pegar do cache do CDN e não vai mais pesar na sua aplicação.</p>
<p>De novo, é outra coisa que remove uma quantidade grande de requisições feitas na sua infra e move tudo pra infra de CDN com outra vantagem: CDNs grandes costumam sincronizar seus dados em servidores de diversas regiões geográficas pelo mundo. Então, mesmo que você tenha feito deploy da sua aplicação num servidor do Brasil, se alguém do Japão acessar, pelo menos todas as imagens, css, arquivos de javascript, podem ser puxados de servidores de CDN do Japão, o que vai proporcionar uma experiência melhor pros usuários. No final todo mundo ganha e sua infraestrutura fica melhor balanceada.</p>
<p>Finalizando, estude estratégias de cache usando componentes como Memcache ou Redis. Estude jobs assíncronos usando serviços como AWS SQS ou Kafka. Estude como monitorar e conseguir métricas reais da sua aplicação em produção. E estude como integrar CDNs no seu front-end. São 4 das principais técnicas de otimização que toda aplicação web precisa. E isso porque eu pulei, mas vale mencionar, que saber fazer queries SQL otimizadas, instalar índices adequados, também melhora tudo uma ordem de grandeza. Só porque seu framework esconde o SQL não significa que você pode ignorar SQL. Um monte de problemas de performance são causados por SQL mau feito.</p>
<p>Mas por hoje vou acabar aqui. Eu só queria rapidamente mostrar alguns dos aspectos que separam uma aplicação web que funciona só na sua máquina, mas que não escala de verdade em produção, e como isso não tem nada a ver com sua escolha de linguagens, frameworks, mas sim do seu não-entendimento de como uma aplicação de verdade realmente funciona. Eu mostrei só o pico do iceberg e tenho certeza que muita gente experiente vai complementar o que falei nos comentários então se ficaram com dúvidas não deixem de mandar aqui embaixo, se curtiram o video deixem um joinha, assinem o canal e compartilhem o video com seus amigos. A gente se vê, até mais.</p>
]]></content:encoded><category>performance</category><category>desenvolvimento-web</category><category>sistemas-distribuidos</category><category>akitando</category></item><item><title>[Akitando] #110 - Como fazer o Ingresso.com escalar? | Conceitos Intermediários de Web</title><link>https://akitaonrails.github.io/2021/12/08/akitando-110-como-fazer-o-ingresso-com-escalar-conceitos-intermediarios-de-web/</link><guid isPermaLink="true">https://akitaonrails.github.io/2021/12/08/akitando-110-como-fazer-o-ingresso-com-escalar-conceitos-intermediarios-de-web/</guid><pubDate>Wed, 08 Dec 2021 11:33:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/0TMr8rsmU-k"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;h2&gt;DESCRIÇÃO&lt;span class="hx:absolute hx:-mt-20" id="descrição"&gt;&lt;/span&gt;
&lt;a href="#descri%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Por que sites como Ingresso.com baleiaram nas vendas da pré-estréia do Homem Aranha? Hoje finalmente vou responder com técnicas de verdade como se faz pra suportar sites de grandes volumes de acessos e transações.&lt;/p&gt;
&lt;p&gt;Vou aproveitar pra explicar rapidamente como cursos e tutoriais levam iniciantes a uma versão correta mas não-escalável, e vou explicar 3 grandes técnicas que sistemas de alta escalabilidade realmente usam no mundo real. Em particular vamos tentar imaginar qual seria a solução pra resolver o problema de escolha de assentos, que é o principal ofensor num site de venda de ingressos.&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/0TMr8rsmU-k"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<h2>DESCRIÇÃO<span class="hx:absolute hx:-mt-20" id="descrição"></span>
    <a href="#descri%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Por que sites como Ingresso.com baleiaram nas vendas da pré-estréia do Homem Aranha? Hoje finalmente vou responder com técnicas de verdade como se faz pra suportar sites de grandes volumes de acessos e transações.</p>
<p>Vou aproveitar pra explicar rapidamente como cursos e tutoriais levam iniciantes a uma versão correta mas não-escalável, e vou explicar 3 grandes técnicas que sistemas de alta escalabilidade realmente usam no mundo real. Em particular vamos tentar imaginar qual seria a solução pra resolver o problema de escolha de assentos, que é o principal ofensor num site de venda de ingressos.</p>
<p>DISCLAIMER: o autor não tem nada contra o Ingresso.com e este video não é uma tentativa de denegrí-los, meramente usar de exemplo educativo por ser um dos maiores de seu mercado.</p>
<h2>Conteúdo:<span class="hx:absolute hx:-mt-20" id="conteúdo"></span>
    <a href="#conte%c3%bado" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li>00:00 - Introdução - Homem Aranha</li>
<li>02:02 - Entendendo o ingresso.com</li>
<li>04:13 - POS de Cinemas - sistemas porcaria</li>
<li>05:21 - Final do Checkout</li>
<li>05:43 - Site feito por Iniciantes 1</li>
<li>08:18 - Minha definição de &ldquo;Iniciante&rdquo;</li>
<li>09:00 - Site feito por Iniciantes 2</li>
<li>09:58 - Raio X rápido de apps Web</li>
<li>12:20 - Tempo hipotético de uma Requisição</li>
<li>13:36 - DDoS do Aranha</li>
<li>17:45 - 1a técnica: CDNs</li>
<li>19:24 - 2a técnica: Caching</li>
<li>24:43 - 3a técnica: Jobs em Background</li>
<li>28:26 - memória compartilhada</li>
<li>30:41 - async/await vs jobs</li>
<li>33:51 - O Ofensor: Escolha de Assentos</li>
<li>34:57 - Sala de Espera</li>
<li>39:57 - Resumo da Solução</li>
<li>44:39 - Não faça o que eu falei!</li>
<li>45:19 - Otimização sem Métrica: Perda de tempo</li>
<li>46:37 - Otimização sem Testes: Tortura</li>
<li>47:35 - Conclusão</li>
</ul>
<h2>Links:<span class="hx:absolute hx:-mt-20" id="links"></span>
    <a href="#links" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li>Thread no Twitter: <a href="https://twitter.com/AkitaOnRails/status/1465390018961289217?s=20"target="_blank" rel="noopener">https://twitter.com/AkitaOnRails/status/1465390018961289217?s=20</a></li>
<li>Build a Virtual Waiting Room with Amazon DynamoDB and AWS Lambda at SeatGeek: Build a Virtual Waiting Room with Amazon DynamoDB and AWS Lambda at SeatGeek | AWS Architecture Blog</li>
</ul>
<h2>SCRIPT<span class="hx:absolute hx:-mt-20" id="script"></span>
    <a href="#script" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Olá pessoal, Fabio Akita</p>
<p>Alguns dias atrás eu fiquei putaço tentando comprar meus ingressos pro novo filme do Spiderman. Se você tá assistindo esse video muito no futuro, em 29 de Novembro de 2021 os sites de venda de ingresso abriram pra vender ingressos no mundo todo e todos os sites ficaram de joelho frente à demanda de gente querendo comprar. O filme vai estrear dia 16 de Dezembro e o hype ao redor dele é quase comparável ao que foi Avengers Endgame, é absurdo. Se o filme entregar metade do hype, já vai ser épico.</p>
<p>Agora, o que não foi épico, foi como os sites de vendas de ingresso se comportaram, especialmente nas primeiras horas. Se você tentou comprar naquele dia, sabe do que estou falando. Foi horroroso, foi tenebroso. Como sempre, sites de ingressos continuam sendo uma grande merda. Vamos colocar em contexto. Um site como o ingresso.com existe faz mais de década. Não lembro quando lançou de fato, mas o domínio foi registrado no ano 2000. Não é um site novo.</p>
<p>É possível que eu esteja sendo mais arrogante que o normal aqui por causa da experiência frustrante que foi ficar 1 hora tentando comprar sem sucesso, enquanto sala após sala ia ficando cheia e o site continuava me dizendo &ldquo;verifique sua conexão com a internet&rdquo;, tipo, mensagem de erro genérica do tempo de internet discada. Vai se fuder.</p>
<p>Então vou aproveitar o calor do assunto pra explicar porque eu acho que o site tem tantos problemas em eventos como esse, como um iniciante faria um site desses e como uma empresa desse tamanho e dessa idade já deveria estar em termos de arquitetura em 2021. Hoje o episódio vai ser bem mais prático mas ao mesmo tempo não vou mostrar código particular de nenhuma linguagem, vou explicar conceitos que servem pra todas as linguagens em projetos de verdade. Já aviso desde agora que o episódio de hoje vai ser super denso. Dava pra picotar esse video em meia dúzia, mas preferi falar tudo de uma só vez. Então preparem o bloco de notas e vamos lá.</p>
<p>(&hellip;)</p>
<p>Vamos começar do começo. Um site como um ingresso.com é basicamente um e-commerce. O produto do site são assentos em salas de cinema. O estoque é limitado, existem somente uma quantidade X de assentos pra cada filme, em cada sala, em um determinado horário. Uma vez que encheu a sala naquele horário, não tem mais o que vender. Não é muito diferente em conceito a um site de venda de assentos pra vôos, como um decolar.com por exemplo.</p>
<p>Se você é iniciante, provavelmente consegue delinear os principais elementos. Certamente começaria criando tabelas pra cadastrar cada rede de cinema, depois cadastrar cada sala em cada cidade. Daí em cada sala iria registrar cada assento com cada código independente. A sala provavelmente teria uma imagem com o mapa dos assentos. Depois faria o cadastro de cada novo filme, filmes ativos agora, filmes em pré-estréia, daí associar filmes com salas. A página principal é só uma listagem desses filmes, com categorias ou tags como &ldquo;Em Alta&rdquo;, &ldquo;Em Cartaz&rdquo;, &ldquo;Em Breve&rdquo;. Coisas secundárias seriam festivais, promoções e coisas assim que iriam pra outras tabelas, mas de qualquer forma a página principal ia puxar tudo isso dessas tabelas.</p>
<p>Daí quando escolhe um filme, cai na página separada desse filme. Ele vai puxar as salas associadas com esse filme, as datas em que cada sala vai ter o filme disponível. Você deixa o usuário escolher o tipo de sala que quer como IMAX ou VIP, qual dia que quer assistir, e embaixo vai filtrando a lista de salas. Cada sala tem uma tabela que associa o filme e os diferentes horários disponíveis nesse dia. Aliás, não tente levar ao pé da letra o que estou falando, só estou listando de cabeça. Se você procurar 5 minutos no Google provavelmente vai achar vários tutoriais dando exemplo de como criar tabelas pra um site de ingressos desse tipo.</p>
<p>Agora vem a parte chata. Uma vez escolhido o horário, quando clica em comprar, ele entra num processo de checkout dividido em 3 partes. A primeira parte é finalmente escolher o produto: o assento exato na sala, horário e dia escolhido. Pra fazer isso eu chuto que o site precisa sair da infraestrutura dele pra acessar um sistema de terceiros que realmente gerencia os assentos da sala, que imagino que é proprietário de cada rede de cinema. Porque precisa ser o mesmo sistema que é usado na frente de caixa quando alguém vai comprar o ingresso pessoalmente direto no cinema.</p>
<p>Se não for o mesmo sistema, o site poderia acabar vendendo um assento online que já tinha sido comprado direto no cinema por outra pessoa e aí dá um monte de problemas. Portanto, temos que assumir que sites como o ingresso.com precisam integrar com esses sistemas, que provavelmente devem ter tecnologia da idade das pedras, lentos pra caramba, totalmente instáveis e mau feitos, relíquias do começo dos anos 2000 ainda usando tecnologias dos anos 90. Pra simplificar imagine que o ingresso.com tem um cadastro de endereços IP de um servidor de cinema, com uma API com funções simples como listar assentos num horário e função pra reservar um assento temporariamente e outro pra reservar um assento permanentemente.</p>
<p>Precisa ter uma reserva temporária, que parece que é de uns 2 minutos, porque online o assento ainda não foi pago. Então você segura a escolha do assento até confirmar o pagamento nas etapas seguintes. Uma vez que o usuário escolheu o assento, agora ele precisa escolher se é ingresso inteira, se é meia - que é outra relíquia que não faz sentido existir. Enfim, daí fazer upselling de coisas como pipoca, refrigerante e o que mais der pra enfiar goela abaixo praquela compra de impulso de última hora.</p>
<p>Finalizado essa segunda etapa, finalmente chegamos na hora de pagar. Agora é escolher o tipo de pagamento que pode ser cartão de crédito direto, ou integrando com meios de pagamento como Google Pay, Apple Pay e assim por diante. Confirmado o pagamento, o site envia a confirmação pra reservar o assento permanentemente, envia o ingresso digital pro e-mail do usuário e todo mundo fica feliz.</p>
<p>Um site feito por um iniciante, sozinho ou seguindo algum tutorial ou curso, teria um banco de dados como um MySQL da vida, com tabelas como eu falei antes: tabela de salas com informações como a cidade, estado, endereço, tipo de sala. Daí uma tabela de filmes, com informações como data de estréia, data de pré-estréia, caminho pras imagens como cartaz. Agora teria uma tabela many-to-many que associa a sala com o filme, chamada horário, por exemplo. E assim por diante.</p>
<p>Daí teria um tabela chamada carrinho ou shopping cart, que associa um usuário com uma escolha de sala, filme, horário e o que voltar da API do cinema com informações do assento, items de upselling como pipoca, tipo de pagamento e um estado que indica se ainda está pendente de pagamento, aguardando confirmação e confirmado ou rejeitado.</p>
<p>O iniciante usaria um framework web comum como Django de Python, Laravel de PHP, Rails de Ruby, Express de Javascript ou algo maior como Spring de Java. Na prática, não importa qual você escolher. Os problemas que eu sofri dia 29 de Novembro tentando comprar Homem Aranha não teriam sido mitigados pela escolha de framework nem de linguagem, então literalmente tanto faz qual você escolher. Eu vou explicar porque já já.</p>
<p>Independente do framework, você começaria criando controllers e views, ou páginas como homepage, página de cada filme, página pra escolha de assento, página pra escolha de pipoca e refrigente, página pra escolher tipo de pagamento, página de confirmação pro ingresso. E claro, tô de propósito pulando um monte de outros itens como página de cadastro, login, recuperar senha, mas a essa altura qualquer framework decente já tem dezenas de bibliotecas pra fazer esses itens em questão de minutos. Se você leva dias pra fazer míseras páginas de cadastro de usuário e login, você é um enorme incompetente que precisa aprender a usar o Google. E eu não tô exagerando. Toda vez que eu vejo um projeto que a página de recuperar senha não funciona, eu já sei que foi feito por um completo amador.</p>
<p>De qualquer forma, o projeto seria feito num frameworks desses, todos os elementos num mesmo projeto, conectando num mesmo banco de dados único. Os elementos de deployment seriam um servidor web como NGINX, o banco de dados MySQL ou Postgres da vida, um diretório com o código fonte do projeto, seja em Javascript, Python ou PHP da vida. Normalmente instalado tudo junto num único servidor e pronto. Acabou. Se você se considera um iniciante em programação web, deve ser capaz de fazer uma versão miniatura de um ingresso.com em questão de poucos dias e todos vão chegar nesse mesmo tipo de projeto. Se tem um curso ou tutorial que implementa um site de venda de ingressos, eles vão parar por aqui também.</p>
<p>Aqui eu faço uma tangente. Eu vi alguns gatos pingados reclamando do meu video de Conhecimento Básico pra Iniciantes dizendo que o que eu listei naquele video é coisa demais pra um iniciante. E eles tem razão. Todo mundo parece que considera iniciante como uma pessoa que não sabe nada e vai começar do zero. Não tá errado, mas eu considero &ldquo;iniciante&rdquo;, todo mundo que já tem conhecimento suficiente pra entrar no mercado de trabalho, ou seja, sabe o mínimo pra não ser um mala sem alça, um peso morto. Sempre que eu falar iniciante é isso: alguém que já minimamente sabe programar. E sim, pra mim um iniciante deve ser capaz de codificar o que eu acabei de explicar aqui em aproximadamente 1 ou 2 semanas. Não mais que isso.</p>
<p>Um iniciante que não consegue fazer uma versão miniatura do ingresso.com ainda não é iniciante, é um hobista, ou um estudante. A versão que todos vocês vão conseguir fazer vai funcionar pra ir pro ar, suportar algumas dezenas ou com sorte, até centenas de usuários. Mas nunca, jamais, nem de perto conseguiria suportar a pré-estréia do Homem Aranha ou um evento nível Avengers Endgame. Jamais. Eu já disse isso várias vezes antes e vou repetir: a grande maioria dos cursos é feita pra ser simples, didática, e fazer você achar que aprendeu algo útil. Mas eles não ensinam o que vou explicar a partir de agora, simplesmente porque a maioria das pessoas que escrevem esses tutoriais nunca trabalharam em sistemas grandes. E isso faz uma diferença enorme.</p>
<p>Mas não tem nenhum problema. Ninguém em sã consciência usaria o código de um iniciante pra um evento desses. Ou pelo menos não deveria. Agora é hora de eu explicar qual é a diferença de um e-commerce feito por um iniciante e um feito por um sênior com experiência de verdade. Vamos começar do começo.</p>
<p>Tudo que um usuário fizer no seu site, listar filmes, procurar salas, comprar pipoca, serão requisições HTTP pra um servidor web, como um NGINX. Esse servidor vai pegar a URL e mandar pra sua aplicação feita em Django ou Express da vida, via proxy reverso. Se você não sabe o que é NGINX ou proxy reverso, vai colocando no seu bloquinho de anotações pra estudar depois. Todo framework vai pegar essa URL, as informações que vieram no cabeçalho HTTP como cookies, a informação de sessão pra saber se é um usuário logado ou um não-logado, passar pra algum tipo de processamento de rota e enviar as informações da requisição pra uma classe de controlador, pra chamar uma função como <code>index</code> de uma classe FilmeController, por exemplo, que vai listar todos os filmes.</p>
<p>Esse controlador vai precisar da ajuda de outras classes como repositórios ou modelos, que no fim mapeiam pras tabelas no seu banco de dados. Talvez tenha um modelo chamado Filme, que vai fazer uma pesquisa SQL no banco de dados MySQL. Agora o MySQL precisa processar a pesquisa, e enquanto faz isso, desde a ponta do navegador do usuário, passando pelo NGINX, passando pelo processo do seu framework, tá tudo bloqueado, esperando o MySQL retornar. Isso é muito importante de entender, que tudo fica pendurado esperando o próximo passo terminar.</p>
<p>Quando o MySQL retornar o resultado da pesquisa, agora o modelo vai processar essa informação e transformar numa lista de objetos, e entenda que isso mais que duplica a memória do resultado porque você tem a informação crua que voltou do banco e a transformação disso em objetos, que ocupa mais que o dobro de memória por conter diversos metadados como conversão de dados e tudo mais. Agora essa lista de objetos passa pra processamento de alguma classe de visualização que vai pegar esses objetos, um template de Django, um template Blade de Laravel, enfim um template de HTML que tem espaços em branco pra serem preenchidos pelos objetos que voltaram do MySQL.</p>
<p>Quando o HTML final for gerado, agora o framework devolve isso pro NGINX, que vai devolver pro navegador do usuário que estava esperando até agora. E finalmente o navegador desenha a página na tela pro usuário poder continuar. Do momento que o usuário digitou a URL até receber a página de volta, digamos que demorou 100 milissegundos, que não é muito ruim. Parte desses milissegundos depende mesmo da conexão de internet do usuário que é o caminho do navegador até o servidor. Digamos que foi 10 milissegundos.</p>
<p>Uma vez no servidor, daí tem o tempo do NGINX processar e mandar pra sua aplicação, e isso costuma ser muito rápido, digamos 5 milissegundos. Agora o framework precisa processar a sessão, a rota, carregar o controlador e tudo mais, digamos que isso leve 20 milissegundos. Daí chega na parte que conecta no servidor MySQL e precisa esperar o SQL processar a pesquisa, e isso vai ser mais uns 30 milissegundos. Finalmente, com os dados e o template em mãos, precisa gerar o HTML final, e digamos que isso leve 25 milissegundos. E com isso, os 10 milissegundos restantes é pra devolver o HTML pro navegador.</p>
<p>No geral, a gente fala que a escolha do framework não importa tanto porque dos 100 milissegundos totais nessa requisição de exemplo, você só vai impactar 2/3 do tempo se trocar. O resto do tempo é rede e conexão com serviços externos como o MySQL. 1/3 do tempo foi no banco de dados e isso não vai mudar só escolhendo um framework diferente. Ah sim, daí o iniciante vai logo mandar um &ldquo;ah, é só trocar o MySQL por MongoDB&rdquo;. Não sua anta. Não faça isso. Agora você escolheu girar um parafuso com um martelo. Num parafuso pequeno parece que funciona porque você bate com o martelo e na força bruta ele entra na madeira, mas isso Não vai continuar funcionando assim por muito tempo. Vou mencionar NoSQL mais pra frente.</p>
<p>Agora, imagine a situação do evento do Homem Aranha nesse site de iniciante. Em questão de segundos seu site estaria efetivamente sofrendo o equivalente a um ataque de Denial of Service. Imagine o seguinte cenário: deu meio dia, todo mundo do Brasil inteiro digita <code>ingresso.com</code> nos navegadores pra abrir a homepage do site. Pronto, ninguém vai conseguir. Seu servidor não vai aguentar e vai abrir o bico.</p>
<p>Eu estou usando números hipotéticos neste episódio inteiro porque dependendo de como você programar, em que tipo de servidor instalar e tudo mais, os números variam. Não existe um número absoluto igual pra todo mundo. Mas digamos que, no mínimo, a requisição pra montar a homepage leve esses 100 milissegundos, como expliquei. Agora digamos que você instalou num servidor baratinho de 8 cores que conseguiria processar 600 requisições por segundo. É até bastante coisa. Mas na realidade é menos que isso, porque seu servidor de banco de dados tem um limite de umas 100 conexões de cada vezes. Então vai ser menos de 600 requisições por segundo, porque algumas requisições precisam esperar outras terminarem de pesquisar no banco antes de pode prosseguir, então digamos que isso vai cair pra uns 400 por segundo. Vocês tem que ter em mente que tudo tem limites, velocidade teórica não é a mesma coisa que velocidade na prática.</p>
<p>Mas pra atingir esse pico, precisa de menos de 400 usuários por segundo. Especialmente quando o mesmo usuário tentar abrir vários navegadores e ficar apertando F5 o tempo todo pra carregar. O que acontece no servidor? Até chegar no NGINX não costuma ter problema, o NGINX não precisa processar quase nada, é muito rápido e consegue aguentar milhares de requisições por segundo nessa máquina. O problema é quando atingir sua aplicação.</p>
<p>Sua aplicação talvez consiga ir um pouco acima de 400 requisições por segundo, mas o banco de dados não consegue, não só pelo limite de conexões, mas porque pesquisas custam tempo de processamento. Toda requisição que não conseguir ser atendida porque a CPU já tá em 100% vai começar a entrar numa fila do NGINX. Mas a fila também não é infinita. E como tá tudo na mesma máquina, chega uma hora que o NGINX, sua aplicação e o banco de dados vão estar brigando por tempo de CPU e por memória, que já tá no máximo, e todos vão começar a ficar lentos até chegar num ponto onde o gerenciador de memória do sistema operacional vai chegar num pico. A memória vai acabar e um deles vai crashear. E quando o banco ou sua aplicação crashear, daí toda requisição que tava na fila vai receber uma mensagem de erro 500 de volta.</p>
<p>Daí sua aplicação reinicia, mas aí todo mundo que tava na fila esperando, recebeu mensagem de erro, vai todo mundo tentar dar reload no navegador, em segundos sua fila vai encher de novo, sua aplicação vai crashear de novo, e vai ficar nesse loop infinito de crash até que uma boa parte das pessoas comece a desistir e vai diminuindo a quantidade de requisições até o limite onde o servidor começa a conseguir responder pra algumas pessoas. Entenda, as pessoas vão fazer uma de duas coisas: ou esperar até conseguir comprar ou esperar até a frustração não valer mais a pena. Do jeito que sua aplicação foi feita, você vai forçar a grande maioria das pessoas a desistir por frustração.</p>
<p>De novo, não tem nada de errado nisso. É nível iniciante, ninguém espera usar sua aplicação pra um evento desses. Mas é um aviso pra quem contrata juniors pra fazer aplicação nível avançado mas não entende a diferença e acha que pode pagar barato pra ter o mesmo resultado. E não, a culpa não é do iniciante. Um hospital não colocaria um residente pra fazer um transplante de coração. Se colocar e o paciente morrer, a culpa não é do residente, é do hospital que obrigou ele a fazer um procedimento que não era qualificado pra fazer. Mas o ingresso.com de verdade pelo menos não tá no nível totalmente iniciante. Quando eu tava tentando comprar, um comportamento era bem óbvio: o site não baleiava na homepage. Se você tentou comprar naquele dia também, vai lembrar que a página principal e a página do filme carregavam até que rápido, sem nenhum problema.</p>
<p>Existem algumas razões pra isso. Você poderia ter uma aplicação web separada pra páginas de conteúdo e outra aplicação só pra fase de checkout, que são as 3 etapas que eu falei de escolher assento, escolher tipo de ingresso e fazer o pagamento. Sendo aplicações separadas, você tem mais oportunidade de escalar a aplicação de conteúdo que é mais leve e tomar porrada numa infra separada pro checkout.</p>
<p>Mas eu acho que a solução é mais simples do que isso. Pra começar, ele não roda a aplicação web junto com o NGINX e junto com o banco de dados, no mínimo cada um deles está em servidores físicos separados. Em segundo lugar, provavelmente usa CDNs pra cachear assets e conteúdo estático. Veja o código fonte da página inicial do ingresso.com. Todo lugar que precisa carregar coisas como stylesheets de CSS, scripts javascript, imagens e tudo mais, ele não puxa do domínio ingresso.com e sim do domínio ingresso-a.akamaihd.net. A Akamai, assim como uma AWS Cloudfront, Google Cloud CDN ou Azure CDN, é um Content Delivery Network.</p>
<p>Serviços de CDN tem servidores espelhos espalhados por diversas regiões no mundo. Quando você faz deploy da sua aplicação, também faz upload de todos os assets da sua aplicação pro CDN. Daí o CDN vai começar a espelhar, ou seja, copiar esses assets, ou arquivos, pra dezenas de outros servidores no mundo todo. Quando o usuário tenta abrir a homepage, uma das coisas mais pesadas é baixar todos esses arquivos javascript, imagens, CSS, mas ele não vai bater no seu servidor de aplicação, vai bater nos servidores de CDN mais próximos.</p>
<p>Só de fazer isso, uma porção considerável do peso do seu site é deslocado pra outros servidores e não vai pesar na sua aplicação. Todo framework web que se preza tem suporte a CDNs, de tal forma que na sua máquina de desenvolvimento ele vai puxar javascript, CSS e imagens direto da sua máquina e só em produção vai automaticamente fazer upload do que precisa e converter os templates de HTML pra escrever o domínio do servidor de CDN, como o ingresso.com faz com a Akamai.</p>
<p>Mas não é só isso. Se o ingresso.com estivesse feito da forma ingênua como expliquei, ela não ia conseguir abrir a homepage mesmo com CDNs. Um dos problemas é ter que ir no banco de dados. Toda vez que um processo web faz isso, ela fica bloqueada, esperando o serviço externo retornar. Vai ficando um monte de processos esperando, e se tiver muitos, uma hora vai estourar. Normalmente costuma ter uma configuração de timeout. Ou seja, a aplicação web espera o MySQL retornar ou, se chegar num tempo limite, pára de esperar, corta a conexão e devolve erro 500 pro usuário.</p>
<p>Eu falei que no nosso exemplo, o MySQL demora uns 30 milissegundos pra devolver. Eu posso colocar um timeout de meio segundo. Mas aí aquele monte de gente conectando tentar abrir trocentas conexões pro banco, e todo mundo pendurado por pelo menos meio segundo, enquanto o MySQL tá sofrendo pra conseguir devolver uma resposta, porque não tem CPU sobrando na máquina pra processar tanto SQL. E o banco tem limite de conexões. O NGINX tem limite na fila. Ambos tem timeouts, e vai ser uma cascata de timeout com um tráfego do nível da audiência do Homem Aranha. A grande maioria vai tomar erro 500, que no caso do ingresso.com ele devolve aquela mensagem tosca de “verifique sua conexão com a internet”, que não tem nada a ver.</p>
<p>Existem várias coisas que podem mitigar isso, por exemplo, garantir que suas tabelas tenham índices corretos pra cada tipo de pesquisa. Índices são como tabelas extras pré-compiladas. Quanto mais índices você botar numa tabela, mais rápido vai ser determinadas pesquisas, mas mais lento vai ser pra inserir ou excluir uma linha nessa tabela, porque precisa também atualizar os índices. Então você corta tempo de pesquisa pra aumentar tempo de inserção. Mas como num site como esse você tem um volume muito maior de pesquisas do que de inserções, sempre é uma boa prática colocar índices corretos. A única pessoa que sentiria alguma lentidão extra, se existir, seria o administrador pra cadastrar novos filmes e novas salas, e isso é uma atividade que ele precisa fazer só uma vez por semana, se muito. Mas só adicionar índices vai ajudar mas não vai resolver nosso problema.</p>
<p>A homepage, durante um dia inteiro, é igual o tempo todo, pra todo mundo. Se você carregar a página ao meio dia, que é quando o filme do Homem Aranha ficou ativo, até a meia noite do mesmo dia, eu tenho certeza que nada mudou. Aliás, se a página é sempre igual mesmo se for num período super curto de 5 minutos, tá excelente, porque se você tá tendo 10.000 requisições por minuto, quer dizer que por 5 minutos você só precisa gastar o tempo de processar o HTML da homepage uma vez e servir a mesma página pra todas as 50 mil requisições durante esses 5 minutos. É uma economia monumental.</p>
<p>Isso é o comum na grande maioria de sites de conteúdo, incluindo e-commerces, onde é raro produtos mudarem com muita frequência. O que pode mudar são informações secundárias, como disponibilidade ou estoque do produto. Por exemplo, a página do filme talvez tenha a mesma listagem de salas, mas à medida que as salas forem ficando cheias, precisa desabilitar da tela a opção de conseguir escolher assentos nela. Mas a listagem das salas em si é a mesma. Só se um administrador esqueceu de adicionar uma sala, e cadastra de última hora, que vai mudar alguma coisa. Mas no geral, a página é a mesma.</p>
<p>E de novo, todo framework web que se preza suporta configuração de cache. Eu posso simplesmente dizer que a página de índice do FilmeController, depois que gerar o HTML da primeira vez, na próxima requisição em diante, por 5 minutos, não processa mais nada. Por um período de 5 minutos, ele vai devolver o HTML direto de um cache. Um cache seria basicamente o HTML que foi gerado gravado como um arquivo estático no sistema de arquivos ou guardado em memória mesmo.</p>
<p>Lembram dos tempos que eu falei? A requisição total levou 100 millissegundos no nosso exemplo. 10 millissegundos na entrada e 10 millissegundos na saída foram só o tempo de internet mesmo. 5 milissegundos foi o NGINX. Todo o resto foi a aplicação web conectando no banco, esperando processar a pesquisa, montando o HTML final com os templates. Se cachear o HTML final significa que podemos eliminar esse tempo todo. Vamos cortar os 30 milissegundos da pesquisa no banco e 25 segundos pra montar o HTML. Digamos que achar o cache e devolver ele leve só 10 millissegundos.</p>
<p>Então dos 100 milissegundos originais, os próximos usuário vão esperar só 35 milissegundos pra receber a página. Ou seja, sem mudar framework, sem mudar linguagem, sem mudar banco, dá pra reduzir quase 60% do tempo original. Por isso eu falo que todo mundo que sugere mudar de linguagem ou banco como primeira opção é certamente um iniciante. Porque você consegue diminuir bastante o tempo de resposta só sabendo usar um recurso que seu framework provavelmente já tem e você que não sabe como usar, que é habilitar e configurar o cache.</p>
<p>Gerenciar cache é uma técnica média, nem iniciante e nem avançado. Se você já tentou e ficou preocupado em apagar cache antigo, tá fazendo errado. Cache tem time to live ou TTL, você diz pra durar 5 min, durante esse período devolve o que tá cacheado e quando expirar, daí passa pelos controladores, modelos, views e tudo mais de novo pra renderizar o novo HTML e cachear de novo. E cache é quem faz a maior diferença numa aplicação web. Você pode cachear uma página inteira, só componentes dentro dessa página. Vale a pena aprender os detalhes das diferentes estratégias de cache como entender o que é o status 304 Not Modified do protocolo HTTP cuja estratégia implementada nos frameworks se chama Conditional HTTP Caching.</p>
<p>Vou pular da página principal pra última ponta do checkout que é o pagamento, ainda tem outra coisa que é importante explicar antes de continuar. Na ponta de pagamento tem gente que faz errado e atrela a página de confirmação de pagamento com o pagamento em si. Isso é comum quando você não usa um serviço de pagamento como Stripe, PagSeguro, Paypal ou semelhante, ou seja, quando você precisa integrar manualmente com um integrador de cartão de crédito, por exemplo. Acho que hoje em dia isso não é mais tão comum porque um MercadoPago da vida é bem mais simples de integrar, por outro lado costumam cobrar taxas maiores, daí depende do seu modelo de negócios.</p>
<p>O modelo de pedido ou ordem de compra costuma ser implementado como uma máquina de estado. Ele inicia com um estado como “pendente de pagamento”, daí tem o pagamento em si que transiciona o modelo pra “pendente de confirmação”, daí espera o integrador de pagamento responder, e transiciona pra “confirmado e pendende de entrega” - caso seja um e-commerce de produtos - ou então transiciona pra “rejeitado”, e registra o motivo dado como número de cartão inválido. Dali pode transicionar de volta pra pendente de pagamento, por exemplo. Se você nunca ouviu falar de máquinas de estado, estude sobre isso. É conhecimento básico.</p>
<p>De qualquer forma, se você fizer do jeito ingênuo, vai fazer um controlador ou uma página que recebe os dados de cartão de crédito, daí vai chamar a API do serviço de forma síncrona, ficar esperando a resposta - que você não tem controle porque é um serviço externo. E aí corre o risco dele demorar, dar timeout e estourar o tempo do servidor web, e ter que devolver um erro 500 e ficar com o pedido num estado inconsistente, com ainda pendente, sem saber se o serviço de pagamento foi até o fim ou não. Daí normalmente vai acabar contratando um monte de gente de suporte pra ficar conciliando os pedidos manualmente.</p>
<p>A mesma coisa vale pra atividades pesadas num banco de dados, onde você manda um SQL pesado, fica esperando, dá timeout e não sabe se foi até o fim ou não. Quando você tá sozinho na sua máquina, codando, isso não faz diferença, porque você tem poucos dados, só tem você acessando, então raramente vai cair numa situação que dá timeout e quebra seu fluxo. Mas em produção é o que mais vai ter: com dezenas de usuários simultâneos todos concorrendo pra usar um recurso escasso que são serviços externos como banco de dados ou pagamentos ou qualquer outro serviço de terceiros.</p>
<p>Por isso você deve entender máquinas de estado e entender o que é processamento assíncrono. Alguns devem pensar, “ah eu sei o que é isso, eu sou de Javascript ou C# ou Go ou alguma linguagem que suporta o pattern de async/await”. É só chamar a função demorada com async, daí o processo fica livre pra fazer outra coisa, e quando o serviço retornar, vai voltar no await e continua dali. Quem é de Go vai falar a mesma coisa, só soltar um gofunc. Quem for de Elixir vai falar, ah só soltar um Task async. E assim por diante.</p>
<p>E tá errado. A menos que seja um processamento que se você perder não faça diferença, não faça desse jeito pra tudo. Você precisa ter uma solução separada de controle de fila. Num serviço de fila você vai fazer o seguinte: vai jogar numa tabela no banco chamado Fila ou Jobs, o próximo trabalho, ou Job como chamamos. E vai deixar um outro processo separado, que chamamos de Worker, puxar esse próximo Job da fila e processar separado.</p>
<p>Num framework como Rails temos embutido essa funcionalidade num ActiveJob. Num Node.js temos bibliotecas como o Bull que faz a mesma coisa. Num Django temos o Celery. Em Spring Boot de Java podemos usar o Rqueue. O Laravel já vem com um serviço chamado Queues. Hoje em dia a arquitetura básica é mais ou menos a mesma. Eu falei colocar o job numa tabela no banco, mas na realidade a maioria grava o job no Redis. Se você ainda não estudou sobre Redis, deveria, é o único NoSQL que de fato a maioria dos projetos acaba usando na prática.</p>
<p>Eu falei rapidamente de Redis no episódio sobre NoSQL. Mas pense num Redis como um banco de dados em memória que tem a opção de persistir os dados em storage. Dessa forma se ele crashear, não perde os dados como um Memcached. Memcached e Redis funcionam mais ou menos como uma memória compartilhada. Se você é iniciante esse conceito pode parecer estranho, mas isso é porque você tá acostumado a rodar tudo na mesma máquina, daí não enxerga a necessidade de um serviço pra memória compartilhada.</p>
<p>Mas pense agora em produção, onde sua aplicação web pode estar rodando em vários servidores em paralelo, embaixo de um load balancer como o próprio NGINX. O usuário começa a requisição batendo no servidor A, mas à medida que vai navegando, vai bater no servidor C, depois no E, daí volta pro A. E tem que ser tudo transparente, o estado que ele tinha no servidor A tem que ser o mesmo no servidor C. Parte disso é resolvido porque a aplicação vai baixar dados do mesmo banco de dados. Mas parte é porque vai ter coisas num Memcached ou Redis, uma memória compartilhada que todos esses servidores acessam ao mesmo tempo, em particular nossa fila de Jobs.</p>
<p>O Redis ainda tem outra funcionalidade, que chamamos de PubSub ou publisher e subscriber. Quando cadastramos o tal Job no Redis, estamos publicando. E do outro lado pode ter processos em background, os nossos workers que mencionei antes, assinando ou subscrevendo um canal. Quando um novo job é publicado o worker que tá assinando é notificado, daí puxa o job e começa a processar em background.</p>
<p>Então o fluxo é assim: você tem uma operação custosa pra fazer, onde seu processo vai ter que cruzar os braços e ficar esperando e arriscando bater timeout e estourar. Então ele publica o Job numa fila, como num Redis, e vai fazer outra coisa. Daí um processo separado, provavelmente em outro servidor separado, vai puxar esse Job e processar, e nesse worker ele pode levar muito mais tempo porque não tem uma página web ou um usuário esperando pendurado. Mesmo se o worker crashear ou der timeout por algum motivo, você tem o registro do Job na fila que é persistida e pode mandar outro worker reiniciar o Job. A maioria dos sistemas de fila dos frameworks tem telas de admin pra você poder manualmente monitorar as diversas filas. Assim mesmo se tudo crashear, você não perde a informação na fila.</p>
<p>E porque eu não posso só soltar um async/await, que vai ser mais ou menos a mesma coisa só que sem a complexidade de um Redis e workers separados? Simples: porque um servidor web tem que estar preparado pra morrer a qualquer momento. Esse é outro conceito que você precisa ter em mente: servidores web devem poder ser derrubados e reiniciados a qualquer momento. Você não deve gravar arquivos localmente num servidor web e nem confiar em deixar coisas importantes na memória RAM. Esse é o conceito de “shared nothing” ou nada compartilhado, que você precisa aprender o quanto antes. A maioria dos problemas de escalabilidade acontecem quando você não sabe disso.</p>
<p>Se precisa gravar arquivos, você vai jogar num serviço externo, como um AWS S3. Que qualquer um dos seus servidores vai conseguir acessar. No mínimo vai gravar num diretório compartilhado na rede. E se precisar deixar coisas em memória, não vai deixar na RAM, vai deixar ou num serviço separado de Memcached ou Redis ou direto no banco de dados, que vai ser bem mais lento. Mas o conceito é que se o servidor web crashear, quando voltar e o usuário bater nele, vai carregar tudo normalmente, sem corromper nada.</p>
<p>Se você soltar dezenas de async nesse servidor e ele crashear, perdeu esses asyncs todos e nunca vai voltar o await. Agora os dados podem ter ficado num estado corrompido. O pedido ficou em estado de pendente e você não tem como saber quem foi confirmado, quem foi rejeitado, e aí como que vai fazer agora? Por isso você mantem jobs como de pagamentos numa fila como no Redis. Mesmo se seu servidor web crashear, os workers não tão nem aí. Eles vão continuar processando e atualizando o estado dos pedidos normalmente. Quando o servidor web reiniciar, ele checa o estado atual do pedido e vai estar correto, porque a fila manteve as informações consistentes.</p>
<p>O que eu acabei de falar deve ter deixado pelo menos metade de vocês meio confusos. Lembram o que eu falei mais no começo? A maioria dos cursos e tutoriais que você acha por aí vai fazer uma aplicação web num framework qualquer, daí vai ensinar e instalar tudo junto num servidor como num Linode ou DigitalOcean da vida. E na sua cabeça tudo fica de pé 100% do tempo, e crashear é uma coisa rara. Quando só tem um usuário testando, você raramente vai enxergar gargalos e falta de recursos, por isso que “na sua máquina tudo funciona”.</p>
<p>Mas na verdade em produção de alta escala de verdade, servidores como de banco de dados, Redis e outros serviços tendem a ser mais parrudos e estáveis, e seus containers de web tem que ser voláteis e permitir crashear a qualquer momento. Novos containers web podem aparecer se precisar escalar pra aguentar mais gente. E quando o tráfego for caindo, eu preciso poder derrubar containers web a qualquer momento pra diminuir os custos. Isso que chamamos de servidores elásticos ou Elastic Compute Cloud como a Amazon costuma chamar seus EC2.</p>
<p>Entendam esse conceito: você deve programar sua aplicação web pra poder ser derrubado a qualquer momento. Por isso tudo que depende de estado local, seja em disco, seja na memória, como async/await, deve ser evitado, ou usado muito pouco em situações específicas e nunca em qualquer lugar sem pensar nas consequências. Você nunca deve sair por aí criando centenas de asyncs locais sem controle nenhum. Sempre pense no que pode acontecer num crash e como você recupera o estado das coisas.</p>
<p>Mas agora chegamos ao principal ofensor do ingresso.com, a escolha de assentos que precisa conectar com o tal serviço bosta proprietário de cada cinema. Esse serviço não temos nenhum controle e podemos assumir que é um grande lixo. Digamos que, se muito, ele aguenta uns 10 usuários simultâneos tentando escolher assentos. Se tiver 1000 usuário de uma vez, 990 vão ficar na fila, vai dar timeout porque escolher assentos pode levar de vários segundos a minutos, e todo mundo vai ficar dando reload na página e isso só vai estendendo a fila até ninguém conseguir fazer nada a não ser desistir e tentar mais tarde.</p>
<p>Como você faria pra resolver isso? E aqui, de novo, não adianta vir com solução fácil como trocar de framework ou trocar de linguagem. É independente, porque o serviço externo não tem como reescrever, e você não tem controle. Quando eu tweetei a solução, falei o seguinte: o serviço externo (estou assumindo) de escolher assento deve ser o pior ofensor e não dá pra escalar. Pra isso deveria haver uma fila, onde cada um que entra por ordem de chegada ganha um ID e espera. Daí vai liberando em batches pra não engargalar a escolha de assentos.</p>
<p>Depois que fiz esse tweet alguém me mandou uma DM com um artigo do blog da AWS. Tem gente que fica me perguntando onde procurar novidades, e assinar os diversos blogs das principais plataformas como blog do Google, da Amazon ou da Microsoft. A tradução do título é &ldquo;Construindo uma Sala de Espera Virtual com Amazon DynamoDB e AWS Lambda na SeatGeek&rdquo;. Eu imagino que SeatGeek seja tipo um ingresso.com lá dos Estados Unidos. E você não precisa usar serviços da AWS pra fazer uma Sala de Espera ou Waiting Room, mas com certeza facilita. Como o blog é da Amazon, obviamente ele vai sugerir usar tudo da AWS, mas entenda que isso é só uma das opções.</p>
<p>O conceito é simples: ninguém entra direto na página de escolha de assentos, que é a parte mais lenta, o pior gargalo, e você não tem controle nem como deixar mais rápido. Então todo mundo entra primeiro numa página de Sala de Espera. Assim como quando você vai nos Correios ou no Cartório, você ganha um código. Quando seu código for chamado, daí você entra na Zona Protegida, que é a escolha de assentos.</p>
<p>A situação de gargalo é quando não tem essa senha. Imagina lá no cartório você chega e fica toda hora perguntando pro atendente, “é minha vez? É minha vez?” Daí o atendente nem consegue terminar a pessoa que já tá atendendo e vai ficando mais lento à medida que chega mais gente. Por isso eles usam o sistema de senhas. Aqui eu vou resumir super resumido pra não complicar o conceito, mas lembre-se que não importa que linguagem ou framework estiver usando. Dá pra implementar em qualquer uma com resultados idênticos. O fluxo é mais ou menos assim:</p>
<p>Primeiro você tem um serviço que gera tokens de acesso únicos, a tal senha de espera. Cada usuário que chega na Sala de Espera ganha um token único associado à sua sessão web. Mesmo se o usuário perder a paciência ficar dando reload na página, vai dar reload numa página cacheada da Sala de Espera, e o token continua sendo o mesmo. Isso evita jogar tráfego desnecessário na página lenta de escolha de assentos. A página de espera em si você coloca um contador em Javascript e usa técnicas como já falei acima de cache, CDN e tudo mais, então você consegue controlar a carga nela e escalar horizontalmente se precisar.</p>
<p>A quantidade de tokens disponíveis vai ser o mesmo número de assentos da sala. Se acabar os tokens, você já de cara diz pros próximos usuários que acabou os lugares nessa sala e manda ele escolher outra sala, que tenha tokens ainda disponíveis. Aqui você vai refinando esse número ao longo do tempo, tipo, já sabe que 10% dos usuários desiste na sala de espera, então da próxima vez distribui 10% a mais de tokens e faz um overbooking, que é o que companhias aéreas fazem também, de reservar mais assentos do que tem no avião porque eles sabem que uma certa porcentagem sempre desiste em cima da hora.</p>
<p>A página do usuário nesse ponto vai ser um timer ou contador que fica falando &ldquo;daqui X minutos mais ou menos, você vai ser atendido&rdquo;. Isso é uma estimativa que você consegue fazer baseado no tempo médio que um usuário costuma levar pra escolher lugares e quantidade de gente na sala de espera. Não é difícil.</p>
<p>A amarração de usuário e token pode ficar armazenado num Redis, como eu vim falando até agora ou, no caso desse blog post, ele sugere deixar no DynamoDB que é outro tipo de NoSQL. Aqui depende da sua aplicação e nenhuma das opções é necessariamente melhor ou pior. Você precisa estudar os detalhes de ambas pra decidir qual é melhor pra você. Eu não recomendo usar Dynamo se você não sabe como ele funciona, as vantagens e desvantagens em cima de outros bancos mais convencionais.</p>
<p>Pra controlar a zona protegida, que é a página de escolha de assentos, você cria um serviço ou um worker que o blog post chama de Bouncer. Se você não sabe, Bouncer é o leão de chácara, o cara grandão na entrada da balada que mantém a ordem. O Bouncer é quem vai ficar monitorando quem já escolheu lugar e deixar outra pessoa entrar pra escolher. Pode ser um de cada vez, pode ser de 10 em 10 pessoas de cada vez. O blog post sugere usar um AWS Lambda, que funciona como o conceito de worker que eu falei acima.</p>
<p>E o AWS API Gateway como a API pra gerenciar os tokens. De novo, é opcional usar esses serviços. Você pode usar o Celery do DJango se estiver fazendo em Python em vez de usar o Lambda. O Lambda vai ser mais conveniente de escalar e instalar, o Celery vai ser um pouco mais barato e mais fácil de programar da primeira vez, mas aí você precisa controlar o deployment e escalabilidade na mão. De novo, depende da sua solução. Você tem que estudar todas as opções antes de sair escolhendo automaticamente só porque um blog post falou.</p>
<p>No final, o importante é que se sua aplicação estiver usando caches como eu falei, CDNs pros assets, evitar ficar batendo em serviços externos como o banco de dados, e implementar uma sala de espera, mesmo com um serviço bosta de escolha de assentos dos cinemas, é possível segurar a multidão do Homem Aranha sem brigas nem empurra empurra. Basta todo mundo ficar na sala de espera e ser corretamente notificado que não adianta esperar porque já tem mais gente esperando do que assentos disponíveis na sala. Daí notifica sugerindo outras salas próximas do mesmo tipo ou outra data mais pra frente.</p>
<p>Fazendo isso não só você controla a carga no serviço bosta e consegue fazer todo mundo pegar o assento que quer de forma ordenada, e consegue dividir o tráfego pra outras salas que as pessoas não escolheriam primeiro. Assim, todo mundo consegue os ingressos que quer, pra data que quer, sem sobrecarregar os serviços. Porque a regra é tornar escalável o que você mesmo fez e controlar o acesso do que você não tem propriedade.</p>
<p>Se subir a aplicação num Heroku ou usando ferramentas como a AWS Elastic Beanstalk ou CloudFormation, ou se for uma plataforma realmente grande usar um Kubernetes da vida, você deve ter servidores web voláteis, que pode escalar pra cima ou pra baixo, ou seja, iniciar novos processos da sua aplicação ou derrubar esses processos, sem perder informações das sessões dos usuários. Os processamentos mais pesados como pagamentos estão sendo controlados por Jobs, seja num Bull de Node.js em outro servidor, seja num AWS Lambda. E as partes mais sensíveis como a escolha de assentos está numa zona protegida por um serviço de Bouncer e seus usuários estão todos em salas de espera que é uma página leve que, mesmo que todo mundo fique dando reload, não vai pesar quase nada, porque a maior parte tá cacheado.</p>
<p>Daí, na frente disso tudo, como o blog post sugere, você ainda pode adicionar um AWS Shield que é um serviço de proteção de Denial of Service e o AWS WAF que é Web Application Firewall. Isso pra mitigar o caso de scalpers, que são robôs de cambistas que ficam tentando comprar ingressos de forma automatizada pra revender mais caro depois num eBay da vida. Outra opção é o Cloudflare, que eu particularmente gosto, pra evitar isso DDoS e cambistas também. Cambista tem que morrer, raça desgraçada.</p>
<p>Eu nem mencionei, mas é claro que coisas como o banco de dados MySQL ou Postgres, que eu prefiro, o Redis, tudo isso hoje em dia você não instala mais na mão e muito menos no mesmo servidor físico que sua aplicação Web. No caso da Amazon vai usar os serviços RDS que é banco de dados como serviço e o ElasticCache que é Redis ou Memcache como serviço. E eles se viram pra aguentar o tráfego que você der pra eles. Tem facilidades como gerar instâncias replicadas e também pra arquivar dados velhos que não usa mais num serviço como Glacier ou RedShift.</p>
<p>Tem muita gente que fica ainda com essa idéia de bala de prata e solução mágica e acha que a solução é só jogar tudo pra um NoSQL como um Dynamo da vida. Mas isso tá errado. Você vai querer extrair relatórios depois, e não tem nada mais prático pra extrair relatórios, fazer auditoria e tudo mais do que um SQL de verdade. Você sempre deve escolher SQL por padrão e pra usar um NoSQL no lugar dele, precisa ter uma justificativa muito forte e muito bem embasada em números. Se for só no chute, não escolha um NoSQL. Caso sua aplicação seja algo com características de alto volume de dados como um FitBit da vida, que fica mandando dados de batimento cardíaco de cada pessoa o dia inteiro, daí vai gerar volumes gigantes de dados que na maioria dos casos é inútil. Você vai compilando agregados desses dados por período de tempo, daí um banco de dados de Time Series pode ser mais adequado que um SQL, mas no final você ainda vai carregar o agregado por período em SQL pra gerar relatórios por exemplo. É caso a caso.</p>
<p>Além disso, tanto num Heroku ou AWS RDS da vida, você tem fácil a opção de fazer replicação dos dados. Ou seja, pode criar uma instância principal e várias instâncias seguidoras que vão receber uma cópia das modificações. Assim você pode extrair relatórios de uma instância que sua aplicação web não acessa, daí não fica concorrendo com seus usuários quando precisar tirar um relatório super pesado, por exemplo, que é outro erro muito comum que iniciantes fazem.</p>
<p>Aliás, se você entendeu o que expliquei até aqui, não só faria SQL pesados de relatórios numa instância replicada separada como jogaria processamento de relatórios pra um Job, pra um worker processar e entregar o arquivo de Excel depois. Se outro administrador pedir o mesmo relatório, já vai ter o cache desse arquivo, daí não precisa processar tudo de novo, toda hora. Sacou? O iniciante ia mandar processar o relatório na mesma instância principal do banco de dados que sua aplicação usa. Nessa hora um monte de tabelas ia ficar bloqueada e sua aplicação web ia engargalar esperando. E se outros administradores pedissem o mesmo relatório, ia mandar processar tudo de novo, deixando tudo lento. Mas usando filas e cache, você minimiza isso.</p>
<p>Filas e cache sozinhos resolvem 80% dos problemas de escalabilidade e isso são conceitos que independem da linguagem ou framework que você tá usando. Um site feito por um iniciante, onde uma página pode levar 100 milissegundos, dá pra baixar pra 30 milissegundos, sem trocar de linguagem nem nada drástico desse tipo. Você consegue aumentar de 1000 usuários simultâneos pra 100 mil usuários simultâneos, sem trocar nada, só fazendo a arquitetura correta como falei.</p>
<p>No tamanho de um ingresso.com, com mais de 10 anos de idade, já era pra ter implementado o que falei, uma parte de cada vez, ao longo dos anos. Ninguém deveria fazer o que eu falei no dia um, porque não se justifica a complexidade da quantidade de peças móveis. Mas a essa altura do campeonato já é injustificável não ser assim. Nada do que eu falei hoje é considerado avançado. São técnicas que existem faz anos, mais de década, todo grande site com as características de ter que acessar serviços lentos como escolha de assentos sabe o que é uma porcaria de uma Sala de Espera. Porra, até a porcaria de cartórios físicos usa esse conceito. O conceito precede a existência da internet.</p>
<p>Outra coisa importante. Eu usei números e métricas inventadas só pra dar de exemplo. Como eu disse, depende do seu sistema, onde você instalou e quantos usuários acessam seu sistema de uma só vez. Pra saber os números reais da sua aplicação, antes de tentar implementar qualquer coisa que falei, você deve ter métricas reais pra comparar antes e depois. Todo framework que se preza tem bibliotecas de profiling e métricas que você deve instalar e rodar em produção. Ou usar serviços como o New Relic, que vai instalar um plugin na sua aplicação web e registrar todos os tempos de tudo da sua aplicação, assim você vai encontrar gargalos muito fácil.</p>
<p>A coisa mais errada que existe é começar a otimizar código sem ter métricas. Você vai gastar dias otimizando uma parte do sistema que achava que era gargalo, mas na real, nenhum usuário nem acessa muito. Otimizar sem métricas é uma enorme perda de tempo. Normalmente tem no máximo meia dúzia de gargalos de performance que se você resolver eles primeiro, 80% dos problemas de performance são resolvidos. Você instala o New Relic, deixa rodando alguns dias, e volta pra ver quem são os Top 3 principais ofensores e vai resolver eles primeiro. De cara, vai descobrir que o principal ofensor é uma tabela grande que você esqueceu de colocar um índice. E fazendo isso metade da lentidão vai embora, sem modificar uma linha de código.</p>
<p>Agora, você instalou o New Relic e vai começar a otimizar, fazer uma fila, transformar um pedaço de código síncrono ou com async num job assíncrono de verdade com workers. Só que ao fazer isso, necessariamente vai introduzir bugs inesperados. Não é uma questão de &ldquo;se&rdquo;, é literalmente uma questão de &ldquo;quando&rdquo;. Não importa se você é um iniciante ou um sênior, você vai introduzir bugs. Especialmente se for a primeira vez fazendo uma arquitetura assim.</p>
<p>Antes de otimizar performance ou mudar arquitetura você precisa criar uma suite mínima de testes. E precisa ir adicionando novos testes e dando manutenção nos antigos, à medida que for implementando a solução nova. Você precisa ter confiança que o que tá fazendo está de fato funcionando. Senão vai ver no New Relic que sim, melhorou a performance, mas só porque você introduziu um bug que tá pulando o código que precisava executar. Daí tudo ficou mais rápido não porque você otimizou certo, mas porque apagou o pedaço que era lento e agora não roda mais. Parece louco isso, mas acontece.</p>
<p>Então antes de falar em fazer Salas de Espera, tokens de acesso, CDNs, Redis e tudo mais, você precisa aprender a instalar e monitorar métricas pra identificar objetivamente os gargalos e erros do seu sistema e precisa aprender pra ontem como fazer testes unitários, testes de aceitação e tudo mais. Só DEPOIS disso que você pensa em adicionar caches e filas e jobs assíncronos. Ficou claro? E espero que com o que expliquei até agora tenha dado uma luz sobre a diferença de sistema feito depois que você só assistiu um curso ou seguiu um tutorial e um sistema de verdade. Tem muito mais coisas além disso. Como eu falei, nunca tive acesso ao código do ingresso.com então estou supondo muita coisa a partir do comportamento que eu experimentei e odiei. Pode ter vários outros gargalos que eu não pensei hoje. Mas é isso aí, se você ficou com dúvidas vai jogando nos comentários, e nada de spoilers do Homem Aranha heim! Se curtiu deixa um like, não deixe de assinar o canal e compartilhar com seus amigos. A gente se vê, até mais!</p>
]]></content:encoded><category>desenvolvimento-web</category><category>performance</category><category>sistemas-distribuidos</category><category>akitando</category></item><item><title>[Akitando] #44 - Concorrência e Paralelismo (Parte 2) | Entendendo Back-end para Iniciantes (Parte 4)</title><link>https://akitaonrails.github.io/2019/03/20/akitando-44-concorrencia-e-paralelismo-parte-2-entendendo-back-end-para-iniciantes-parte-4/</link><guid isPermaLink="true">https://akitaonrails.github.io/2019/03/20/akitando-44-concorrencia-e-paralelismo-parte-2-entendendo-back-end-para-iniciantes-parte-4/</guid><pubDate>Wed, 20 Mar 2019 17:00:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/gYJSWs-gp1g"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;Finalmente! Chegamos ao FIM do assunto sobre Concorrência e Paralelismo! Desta vez vou finalizar o que faltou falar sobre processos, threads, como eles se coordenam, quanto isso custa pro sistema operacional. E então vamos vamos sobre Green-Threads e como as linguagens modernas lidam com elas.&lt;/p&gt;
&lt;p&gt;Então finalmente posso dar minha perspectiva sobre as linguagens interpretadas como Python, Ruby, mais sobre Erlang/Elixir, Go, e como Scala, e outras linguagens se comportam em termos de concorrência e como se comparam com coisas como Javascript.&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/gYJSWs-gp1g"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>Finalmente! Chegamos ao FIM do assunto sobre Concorrência e Paralelismo! Desta vez vou finalizar o que faltou falar sobre processos, threads, como eles se coordenam, quanto isso custa pro sistema operacional. E então vamos vamos sobre Green-Threads e como as linguagens modernas lidam com elas.</p>
<p>Então finalmente posso dar minha perspectiva sobre as linguagens interpretadas como Python, Ruby, mais sobre Erlang/Elixir, Go, e como Scala, e outras linguagens se comportam em termos de concorrência e como se comparam com coisas como Javascript.</p>
<p>Este é o episódio mais longo da série até agora, e se você conseguir chegar até o final deve ter uma visão bem mais completa quando for discutir sobre linguagens. Então tenha um pouco de paciência.</p>
<p>Se você ainda não assistiu os vídeos anteriores, agora é a hora porque este vídeo vai convergir todos os assuntos que venho explicando desde que comecei a falar sobre Back-end.</p>
<p>Links:</p>
<ul>
<li>Named Pipe or FIFO with example C program (<a href="https://www.geeksforgeeks.org/named-pipe-fifo-example-c-program/"target="_blank" rel="noopener">https://www.geeksforgeeks.org/named-pipe-fifo-example-c-program/</a>)</li>
<li>Understand promises before you start using async/await (<a href="https://medium.com/@bluepnume/learn-about-promises-before-you-start-using-async-await-eb148164a9c8"target="_blank" rel="noopener">https://medium.com/@bluepnume/learn-about-promises-before-you-start-using-async-await-eb148164a9c8</a>)</li>
<li>Futures, cancellation and coroutines (<a href="https://medium.com/@elizarov/futures-cancellation-and-coroutines-b5ce9c3ede3a"target="_blank" rel="noopener">https://medium.com/@elizarov/futures-cancellation-and-coroutines-b5ce9c3ede3a</a>)</li>
<li>Futures and Promises (<a href="http://dist-prog-book.com/chapter/2/futures.html"target="_blank" rel="noopener">http://dist-prog-book.com/chapter/2/futures.html</a>)</li>
<li>Go concurrency considered harmful (<a href="https://medium.com/@sargun/go-concurrency-considered-harmful-26499a422830"target="_blank" rel="noopener">https://medium.com/@sargun/go-concurrency-considered-harmful-26499a422830</a>)</li>
<li>Go: the Good, the Bad and the Ugly (<a href="https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/"target="_blank" rel="noopener">https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/</a>)</li>
</ul>
<p>=== Script</p>
<p>Olá pessoal, Fabio Akita</p>
<p>O episódio da semana passada foi bem pesado em conceitos. Estamos no nono episódio da série Começando aos 40, episódio quatro do tema de Back-end, e a segunda parte especificamente sobre Concorrência e Paralelismo. Pra variar, eu tentei encurtar esse episódio mas ele vai acabar virando um dos mais longos da série de novo. Se você ainda não assistiu meus episódios anteriores do tema de Back-end, eu recomendo que pause agora e vá assistir, porque tudo que eu falei antes vai convergir hoje.</p>
<p>Como eu já disse, é um tema bem cabeludo e vamos tentar desembaraçar os últimos conceitos que vocês vão precisar saber pra conseguir seguir com seus estudos sozinhos. A pergunta que todo mundo fica na cabeça é: qual das linguagens é a mais performática? Ou, qual vai me dar a escalabilidade do Whatsapp e ao mesmo tempo ser mais fácil de programar?</p>
<p>Vários números podem parecer impressionantes. Muita gente adora repetir os X milhões de conexões simultâneas que um servidor do Whatsapp consegue suportar e chegam à conclusão que não existe nada melhor do que Erlang. Esse número é bacana, mas não é impressionante. Parem de se impressionar com números sem contexto. E parem de ficar escolhendo tecnologia baseado em blog post ou palestra de engenheiro do Netflix ou Facebook. É um absurdo sem sentido pra pessoas que deveriam ter como profissão raciocinar com lógica.</p>
<p>Repitam comigo: você não é o Netflix. Seu produto que ainda nem saiu do papel, não tem nem um usuário, você não precisa nem pensar na escalabilidade massiva de um Netflix, que em horas de pico pode representar ⅓ do tráfego total da internet dos Estados Unidos. Quando alguém me pergunta: Akita, queria fazer tal coisa igual do Facebook, será que dá? Minha resposta é a mais óbvia: claro que dá, você está disposto a PAGAR o mesmo que o Facebook pagou? Se sim, então claro, dá pra fazer igual ou melhor. Guardem esse pensamento, eu ainda vou falar sobre isso em mais detalhes em outro episódio, mas por agora só entendam esta verdade: foda-se mil conexões simultâneas ou 10 milhões de conexões, sua preocupação como iniciante tá a milhares de quilômetros de distância disso. Atenha-se a entender as tecnologias sem virar fã de marcas e ter metas fora da realidade.</p>
<p>(&hellip;)</p>
<p>Já entendemos os mecanismos básicos de como conseguimos ter concorrência e paralelismo. Mas na minha opinião o real problema é a coordenação. Não adianta nada você ter silício suficiente pra executar milhares de threads em paralelo se você não consegue coordenar o trabalho dessas threads. Então se tiver uma única coisa que você precisa levar dessa série toda é que a coisa mais importante nesse tipo de programação é conseguir a maneira mais simples e “barata” de coordenar tarefas concorrentes.</p>
<p>Vamos voltar ao que interessa. Você é um iniciante, finalmente entendeu os conceitos mais básicos de processos e threads. Aprendeu rapidamente sobre forks, sobre copy on write que ajuda a economizar memória. Mas você precisa entender alguns conceitos adicionais sobre coordenação agora. A unidade mais simples numa CPU é uma instrução. Uma instrução em Assembly, que usa como argumentos certos valores que você preenche em determinados registradores. Daí a instrução executa e o resultado é escrito pela CPU em algum acumulador ou outro registrador. Ele incrementa o contador de programa e vai pra próxima instrução.</p>
<p>É assim que a CPU começa a transformar o mundo ao seu redor. Tudo que você programa serve para pegar alguma informação que o mundo exterior vai fornecer, por exemplo o que o usuário digita ao programa, e você vai usar essa e outras informações para gerar algum resultado. Programação é basicamente isso: transformação de dados. Uma instrução simples de soma, pega dois números que você armazena um em cada registrador, a instrução executa o cálculo e transforma esses dois números num único que representa a soma deles, e grava em outro registrador. Essa instrução é basicamente o que você chamaria de uma função. Toda função recebe parâmetros ou argumentos de entrada, processa alguma coisa e retorna algum valor de saída. E você pode usar o resultado de uma função como parâmetro ou argumento de outra função, e então vai  encadeando uma função atrás da outra.</p>
<p>Quando você manda o sistema operacional executar o binário do seu programa, digamos um programa como wget ou curl no Linux, que você pode pensar como se fossem navegadores Web muito simples que rodam na linha de comando, você normalmente passa uma parâmetro, no caso uma URL de algum arquivo que você quer baixar. Você já viu ou vai ver um monte de tutoriais que usam um desses dois comandos pra baixar alguma coisa. O OS vai fazer o que já expliquei antes: alocar memória, criar um processo isolado, carregar seu binário, e passar esse parâmetro pro processo. Esse processo vai executar, no caso conectar no servidor que você mandou e baixar o tal arquivo. E quando terminar, o OS vai descarregar e liberar os recursos e o resultado, se tudo deu certo, pode ser um arquivo gravado em algum lugar no seu sistema de arquivos. Nesse caso o programa funciona como uma “função” também, ele tem uma entrada que é a URL que você passou de parâmetro e tem um retorno que foi o arquivo gravado. Ele transformou uma URL em um arquivo.</p>
<p>Em sistemas UNIX ou Linux, os shells como BASH ou ZSH, onde você roda na linha de comando, permitem encadear esses programas como se fossem funções. Quando seu programa carrega e vira um processo o sistema cria três canais unidirecionais: um canal padrão de entrada ou Standard In ou STDIN, um canal padrão de saída ou Standard Out ou STDOUT e também um outro pra erros, o STDERR.
Na prática pense que todo processo pode iniciar conectando alguma coisa no STDIN e alguma outra coisa no STDOUT. Por exemplo, podemos usar o tal programa CURL pra baixar um arquivo da Web mas em vez de gravar em um arquivo local podemos redirecionar o conteúdo baixado pra esse STDOUT e conectar no STDIN de outro programa, por exemplo, um programa como o famoso grep que filtra texto pra fazer pesquisa. (curl -s <a href="http://somepage.com"target="_blank" rel="noopener">http://somepage.com</a> | grep whatever)</p>
<p>Quando ensinei sobre processos já expliquei que processos sobem em espaços isolados de memória e o código interno do processo não interfere no espaço de memória de outro processo. O que eu acabei de falar sobre encadear processos redirecionando o STDOUT de um ao STDIN do outro, usando o que chamamos de Pipes é a primeira forma mais simples de comunicação e, portanto, coordenação entre processos, mas não é o único e nem necessariamente o mais útil, porque você só liga o final de um programa ao começo de outro programa e esses canais só tem uma direção, ou seja, o primeiro programa é quem manda coisas pro segundo programa, mas não o oposto.</p>
<p>Sem entrar em muitos detalhes hoje, existem outras formas de um processo se comunicar com outro. Como já disse antes existem os sinais como SIGINT. E você pode codificar seu programa pra ignorar isso ou fazer alguma coisa quando recebe esses sinais ou eventos. Novamente é mais uma forma rudimentar de comunicação. Um terceira forma rudimentar é que dois processos abram um mesmo arquivo, daí um deles pode ir escrevendo uma linha nova de cada vez nesse arquivo e o outro processo pode ir lendo essas linhas novas.</p>
<p>Expandindo nessa idéia de um arquivo comum entre dois processos, o sistema operacional oferece outra opção, os FIFOs que se você estudou alguma coisa de estruturas de dados, funcionam como uma fila, First In, First Out, ou seja, o primeiro elemento que entra é o primeiro elemento que sai. Você pode abrir um FIFO e dar um nome e abrir o mesmo FIFO a partir de outro processo, é quase como se fosse um arquivo. Agora os processos podem se comunicar e a essa estrutura damos o nome de Named Pipe. E chamamos de pipe porque é como se fosse um cano mesmo, passando informação de um lado para o outro. Eles são limitados como os pipes simples ou arquivos. Um dos processos abre o named pipe pra ler e o outro pra escrever e por isso também a comunicação só flui numa única direção.</p>
<p>Em todos os exemplos, um dos processos serve de produtor de alguma informação e o outro processo serve de consumidor. E o mecanismo no meio, seja um arquivo ou um named pipe, serve meio como uma fila. Eu acho que o mecanismo mais simples de comunicação entre dois processos é uma fila e o sistema operacional já oferece esses mecanismos faz décadas. Porém, no nosso mundo de Web, internet e programas que funcionam como servidores - que seriam os produtores - e programas que funcionam como clientes - que seriam consumidores, esses mecanismos não funcionam, porque não se comunicam pela rede. Mas mesmo na mesma máquina, sem depender de rede, podemos simular a comunicação entre um servidor e múltiplos clientes usando named pipes ou arquivos. Porém, cada novo cliente conectando no servidor exige um novo named pipe ou arquivo. Por isso existem os sockets.</p>
<p>No caso da internet falamos sobre sockets de IP que trafegam pacotes no formato TCP. Uma das vantagens de usar sockets é que o programa servidor se liga a uma porta no sistema como já expliquei e aceita conexões de múltiplos clientes. Mas diferente de arquivos ou named pipes, o servidor só precisa se comunicar através de um único socket, a partir de onde vai aceitando as conexões de múltiplos clientes. Além de só precisar de um único socket pra abrir esse canal, a comunicação é bi-direcional, então o cliente também pode enviar dados pro servidor e vice-versa, pelo mesmo socket.</p>
<p>No caso de Unix e Linux além de um socket IP existe o que chamamos de Unix Sockets. Ele tem propriedades parecidas com sockets de IP mas só servem para processos rodando na mesma máquina já que não é um socket de rede. É a maneira preferida de fazer dois processos se comunicarem de forma eficiente porque você só precisa de um socket no processo servidor pra atender múltiplos outros processos clientes, que normalmente chamamos de workers, e ele abre um canal bi-direcional, diferente de named pipes. No episódio anterior eu expliquei como o servidor web NGINX tem um processo master, que serve como um servidor, e ele cria diversos processos workers e, claro, eles se comunicam entre si via esses Unix Sockets, canais versáteis bi-direcionais. É como outros servidores que geram forks e workers fazem para seus processos coordenarem entre si, como um Postgres ou MySQL.</p>
<p>No mundo web, onde hoje temos os tais micro-serviços, eles são basicamente processos, programas servidores, que se ligam a uma porta de rede e abrem sockets IP aceitando conexões TCP e em particular oferecendo comunicação via o protocolo HTTP. Mas na prática são processos que se coordenam via sockets. Basta vocês entenderem que sockets, em particular sockets IP, obrigam um processo a se ligar a uma porta; e são canais bi-direcionais de comunicação. Em UNIX como BSD ou clones como Linux, podem ser Unix sockets para comunicação de processos na mesma máquina ou sockets IP para comunicação de múltiplas máquinas numa rede TCP/IP. E no nosso mundo Web essa comunicação normalmente se dá através do protocolo HTTP mas não é uma obrigação, podemos ter outros protocolos mais eficientes como o Protobuf ou Protocol Buffers que é um protocolo em formato binário, que é mais eficiente que o HTTP que é um protocolo texto, inventado pelo Google.</p>
<p>Aliás, lembrem que protocolos binários sempre são mais eficientes que protocolos em texto pelo simples fato que texto ocupa muito mais espaço. A explicação é muito simples. Num computador, representamos tudo no formato binário, 1 ou 0. Você já deve ter aprendido isso então vou resumir muito rapidamente. 1 byte são 8-bits. Em 1 byte você pode representar números inteiros de 0 até 255. Por que 256? Porque 2 elevado a 8 é 256. Uma palavra double byte ou seja 2 elevado a 16 representa números até 65 mil 536.
Um número como 1,000, cabe perfeitamente em 2 bytes, se for representado binariamente. Mas, se você preferir mandar o número 1,000 como um texto, isso seria um texto de 4 caracteres. Em um sistema moderno que reconhece Unicode ou UTF-8, cada letra precisa de 2 bytes ou mais. Existe uma tabelona que mapeia cada caracter de cada alfabeto do mundo a um código. Em alfabetos ocidentais, 1 byte já resolve, mas kanjis de japonês precisa de pelo menos 2 bytes por exemplo. Mas no caso do texto 1,000 que é um, zero, zero, zero, são pelo menos 4 bytes. Em resumo, o número puro sozinho, em binário, cabe em 2 bytes, mas sua representação em texto precisa de 4 bytes, ou seja, o dobro do tamanho. Iniciantes tem dificuldade de entender isso mas comece decorando, o número inteiro 1000 é um elemento totalmente diferente do texto de 4 caracteres “1000”.</p>
<p>Isso fazia mais diferença numa época onde as redes eram extremamente lentas, e hoje você não pensa muito nisso porque qualquer um pode ligar nas porcarias de uma Net ou Vivo e pedir internet de 50 Megabits ou mais. Mas se você tem um sistema que trafega milhões de gigas por hora, todo byte faz diferença, por isso pra muita coisa o Google usa o Protobuf em vez de HTTP em seus sistemas. Pro seu dia a dia, não se preocupe tanto, HTTP tá de bom tamanho mesmo porque hoje sabemos comprimir texto com algoritmos como GZIP que podem derrubar o tamanho de um texto longo até menos de um décimo do seu tamanho. Mas isso é assunto pra outro dia.</p>
<p>Eu expliquei tudo isso porque a comunicação entre dois processos, usando um protocolo pesado como HTTP, sobre TCP/IP, passando pros sockets que seu programa se conecta, vai precisar fazer uma coisa chamada marshalling ou serialização. Ou seja, internamente no seu programa o dado, digamos o tal número 1000 puro, está numa variável que provavelmente é um inteiro de 2 bytes. Mas quando você vai enviar pra fora do seu processo, como a exigência é o protocolo HTTP, você vai serializar ou seja, transformar o inteiro binário pra um texto e cuspir pelo socket. Do outro lado um outro processo cliente vai receber esse texto e vai ter que converter de volta pra um inteiro, ou seja, desserializar o dado. Muitos dos bugs modernos em micro-serviços, ou seja, comunicação de processos através de HTTP, vem de erros nessa conversão e desconversão. Fique esperto com isso.</p>
<p>Mas eu estou me adiantando. Tudo isso foi pra explicar as diversas opções que existem para comunicação entre processos, você tem coisas muito simples como pipes, named pipes, arquivos, unix sockets e até sockets IP. Pra fazer um programinha besta, que só roda localmente, falar com outro programinha, que também roda só localmente na mesma máquina, obviamente usar um protocolo como HTTP é dar um tiro de canhão pra matar uma mosca. É descomunal e ineficiente. Na pior das hipóteses você vai querer usar Unix Sockets. Se você programa em C existem bibliotecas como OpenMPI e OpenMP para facilitar programar comunicação inter processos na mesma máquina ou mesmo entre diversas máquinas.</p>
<p>Entendido como processos se comunicam, vamos entrar dentro de um processo e falar de Threads. Eu já expliquei nos episódios anteriores que uma Thread tem acesso à toda a memória interna do processo. Então se uma thread precisa se comunicar com outra thread, elas podem basicamente compartilhar uma mesma estrutura de dados. No caso de C digamos um Array. Ou uma lista ligada. Como fica tudo no mesmo espaço de memória, não existe a necessidade de você usar mecanismos fora do processo como um named pipe ou unix sockets, porque você acessa o dado diretamente no endereço da memória virtual do processo. Mecanismos externos que eu falei antes existem porque um processo não enxerga nenhum endereço de memória externo ao seu espaço virtual alocado pelo sistema operacional. Por isso comunicação entre threads é ordens de grandeza mais eficiente, mas com essa eficiência vem todos os problemas de mutex, semáforos, bugs de race condition e dead-locks que eu já expliquei.</p>
<p>Programar com threads é um processo que tem que ser extremamente cuidadoso da parte do programador. O sistema operacional não tem como te proteger e você precisa garantir que uma thread não vai escrever em cima do mesmo endereço onde já tem outra thread escrevendo ao mesmo tempo. Num sistema que só opera com uma thread real, esse problema meio que não existe porque o scheduler do sistema operacional vai deixar só uma thread realmente rodar e a outra precisa esperar a vez, o tal do time-slice que eu já expliquei. Mas em máquinas com 2 cores ou mais, realmente 2 threads podem estar rodando exatamente no mesmo instante, com paralelismo real, e é aí que os bugs que eu falei podem acontecer.</p>
<p>Como eu disse antes, o problema todo é coordenação. Pra piorar, vocês vão notar que existe a CPU com seus cores reais, o hardware de silício, que rodam threads reais, uma por core. E existem as threads que o sistema operacional organiza e gerencia. O tal scheduler do sistema operacional como o CFS do Linux, tem acesso a esses cores físicos, mas os programas podem mandar criar 100 Linux threads. Então numa máquina Intel i5 da vida, com quad-core e hyper-threading, existem 8 threads reais que podem rodar em paralelo em cada determinado instante, mas o sistema operacional pode gerenciar centenas ao mesmo tempo, fazendo seu scheduler ir dando um pouco de tempo pra cada uma, fazendo o tal context-switching que eu expliquei, tirando as coisas da mesa de uma thread, recolocando as coisas na mesa pra outra thread e dando vez pra ela.</p>
<p>Para gerenciar essas threads, fazer o tal context switching, o sistema operacional precisa guardar o tal contexto em memória pra cada thread. E eu já disse que uma thread vai gastar pelo menos 1 MB de memória. Então quanto mais threads você criar mais memória vai gastar. Pior, quanto mais threads você criar, maior o trabalho de context switching. E pior ainda, quanto mais threads você criar maiores as chances de seu programa ter aqueles bugs por não ser thread-safe. Existe ainda outro agravante, criar threads é uma atribuição do sistema, mais especificamente da kernel. Para seu programa pedir pra kernel criar threads você precisa fazer system calls, as syscalls que eu falei, e isso também não é barato.</p>
<p>Aliás, se você não sabia, vale definir. Você pode pensar que a kernel do Linux, drivers, os programas que vem numa distribuição, e os próprios programas que você escreveu, é tudo a mesma coisa. São todos binários que rodam na máquina. Se você fez o que eu recomendei e estudou um pouco de Linux, você sabe que existe o comando <code>sudo</code>, o comando que escala seu programa pra rodar com permissões de root ou administrador da máquina. Nesse caso você sabe no mínimo que existem diferenças entre rodar como um usuário restrito normal ou rodar como um root que tem acesso a tudo.</p>
<p>Mas na verdade o root não é a coisa mais poderosa do sistema. O Kernel é que é. No final das contas é o kernel que decide o que o root pode ou não pode fazer. Então se você só chegou até o root, vamos entender outra coisa. A única coisa que tem acesso irrestrito a tudo da máquina, todos os dispositivos, toda a memória e pode executar qualquer coisa, é a kernel. Na arquitetura de processadores Intel existem 4 anéis de permissão. A Kernel boota e roda no chamado Ring 0, ou anel 0, dentro desse anel qualquer código tem acesso a tudo. Todos os seus programas, inclusive os que rodam via sudo, rodam no Anel 3, o mais restrito de todos. É onde rodam os seus programas. Nos Anéis 1 e 2 rodam os drivers e coisas de virtualização, por exemplo o Virtual Box carrega a kernel do OS que ele vai emular no Anel 1. No Anel 3 várias instruções de Assembly da máquina não funcionam, como a instrução que muda de anel, obviamente, ou a instrução HLT que dá Halt e pára a máquina. O sudo simplesmente libera mais system calls pra kernel, mas o programa ainda vai ter que fazer syscalls e pedir coisas pra kernel.</p>
<p>Num processador ARM também existem Rings mas a numeração é ao contrário, o que ele chama de EL0 é o equivalente ao user land, ou Ring 3 da Intel. O EL1 é onde roda a kernel, eu não tenho certeza mas acho que seria mais equivalente ao Ring 1 da Intel, porque no EL3 roda o Hypervisor que vai ser tema talvez do próximo episódio. Daí você pode rodar múltiplos kernels no EL1 isolados um do outro.</p>
<p>O mais importante é entender que existe User land que é onde rodam todos os seus programas e Kernel space, onde roda a Kernel, a única entidade que tem real acesso a tudo do sistema. Chamar uma função dentro do seu próprio processo é extremamente barato. Chamar uma syscall é mais ou menos como chamar uma função em outro processo, então você pode imaginar que vai acontecer algo parecido com um context switch toda vez. Fora isso, existe um salto em privilégios do Ring 3 pro Ring 0. Tudo isso custa. Ok, são micro-segundos a mais toda vez. Mas pense um servidor web com threads, com milhares de usuários pedindo requisições HTTP e seu servidor criando threads pra cada uma e matando todas que vão acabando. Milhares por segundo, milhares de syscalls por segundo, agora as coisas começam a ficar caras.</p>
<p>Não tem nada mais rápido do que código que roda dentro do seu processo. Por isso você quer evitar fazer syscalls o máximo que puder. Criar milhares de processos ou milhares de threads custa memória, custa context switch, custa escalar privilégios entre Rings. Parece que o povo do Node.js estava correto em usar I/O assíncrono, certo? Calma, I/O assíncrono e event loops ou reactors é só “Uma” forma rudimentar de concorrência em sistemas que não suportam outros mecanismos como threads. E justamente, Javascript não suporta threads. Ele é mono-thread, então a sua única opção é usar o intervalo entre atividades de I/O como arquivos ou sockets pra tentar fazer alguma coisa. Mas isso é muito pouco.</p>
<p>Eu já expliquei uma das soluções no episódio anterior. É fazer o que o NGINX faz: múltiplos processos, na média um pra cada core da sua máquina, ou seja, um processo pra cada thread e cada uma delas com um reactor pra aproveitar que chamadas de I/O costumam gastar tempo pra completar. A grande vantagem num sistema como Node.js que é um processo de um único-thread é que você tem alguma concorrência graças às syscalls de I/O assíncrono do sistema operacional, e pelo menos não tem que lidar com as dores de cabeça de mutex e bugs de sincronia como race conditions ou deadlocks. Imagine o inferno extra que ia ser se Javascript além de tudo ainda tivesse que lidar com mais esse tipo de categoria de bugs.</p>
<p>Interpretadores que dependem bastante do sistema operacional por baixo, como Python ou Ruby possuem Threads reais. Porém existe um grande problema. Para ter boa performance, eles delegam muita coisa pra módulos escritos em C. O Scipy, uma das bibliotecas científicas mais famosas do mundo Python é quase inteira escrita em Fortran e C++, por isso ela é veloz, só uma casca é escrita em Python. Essas extensões ganham acesso às estruturas de dados internas dos interpretadores, é como se fossem parte do interpretador.</p>
<p>Agora vem o grande problema: elas não são necessariamente thread-safe, na verdade a maioria não é, e você não tem como saber.  Por causa disso, as Threads de Python e Ruby, apesar de mapearem para threads reais do sistema operacional e terem a capacidade de rodar em paralelo, acabam bloqueadas por um lock gigante do interpretador, que justamente ganha o nome de Global Interpreter Lock, ou GIL.</p>
<p>Com exceção de chamadas de I/O que conseguem rodar realmente em paralelo, as threads do interpretador são bloqueadas por causa do tal GIL. Elas ainda são úteis, mas não tanto quanto numa linguagem que não tenha o GIL. Na prática, assim como Javascript, tanto Python quanto Ruby acabam sendo essencialmente single-threaded, com algumas exceções como operações de I/O. Clones deles como Pypi ou JRuby, como não precisam manter compatibilidade com extensões em C, podem desligar o GIL e usufruir de threads reais.</p>
<p>Assim como Node.js em 2009, eu já expliquei como o Twisted em 2002 implementou a mesma solução de colocar um event loop numa thread pra atender milhares de requisições de I/O assíncronas e usar o intervalo enquanto espera essas chamadas completarem pra executar alguma computação e assim ter um jeito rudimentar de concorrência. Obviamente isso tem duas desvantagens, a primeira é que basta você fazer uma computação que demore demais e o event loop inteiro é bloqueado mesmo que a chamada de I/O já tenha terminado, já que roda tudo na mesma thread.</p>
<p>E a segunda desvantagem é que sua aplicação precisa ter muitas requisições de I/O pra compensar, ou seja, é melhor se usado em servidores de rede, como um servidor de aplicações Web. Esse modelo não funciona pra programas que são pesados em computação só. Por isso Node.js ou Twisted são indicados pra aplicações web. No mundo Ruby temos opções similares como Eventmachine ou o mais recente Async. No mundo Python, depois do Twisted também foi criado o Tornado e o Gevent que tem APIs mais modernas. Na prática é a mesma solução em todos eles: um event loop rudimentar que chamamos de reactor, que é gerenciado numa única thread, intercalando syscalls assíncronas de I/O e executando algum código nos intervalos.</p>
<p>Do ponto de vista da programação do código temos um problema. A forma como se programa um event loop é fazendo uma chamada de função assíncrona e passando como parâmetro uma segunda função que deve ser chamada quando o I/O acabar. Por exemplo, uma função de ler um arquivo de template de HTML na máquina recebe de parâmetro uma segunda função pra preencher esse template e gerar o HTML final. Num sistema single-thread, com tudo bloqueante, você escreveria essas chamadas de função uma embaixo da outra. Mas num sistema orientado a eventos, você precisa passar as funções subsequentes uma como parâmetro da outra. E isso cria o horroroso padrão de cascata ou o que eu gosto de chamar, de hadouken de funções. Se você ver algo assim hoje em dia, é um jeito antigo de escrever, é hediondo e não há ser vivo no planeta que veja algo nesse formato e não sinta imediatamente vontade de vomitar um pouco na boca.</p>
<p>No C já tínhamos algo parecido mas não necessariamente só pra concorrência, é o conceito de passar o ponteiro ou referência de uma função como parâmetro de outra função, chamamos isso de callbacks. Interfaces gráficas em VB ou Delphi ou Java Swing também tinham esse conceito com Listeners por exemplo. É um jeito rudimentar de se encadear funções onde uma depende do resultado da outra dentro de um loop de eventos. Toda interface gráfica é basicamente um loop também. Na prática, você tem um callback pra quando tudo dá certo e outro callback pra quando dá algum erro.</p>
<p>O Twisted do Python introduziu um conceito de código, os Deferreds. Em vez de chamar uma função, você cria um objeto que pode ser encadeado. Esse objeto basicamente engloba a tal chamada assíncrona, por exemplo, e você pode configurar esse objeto chamando métodos como addCallback. É quase a mesma coisa que antes, mas como estamos encapsulando a função num objeto, ele fica um pouco mais maleável de se trabalhar no seu código sem precisar virar um callback hell ou hadouken que falei antes. No mundo Web o framework JQuery adicionou a mesma construção com o mesmo nome, Deferreds. Assim você podia configurar uma chamada Ajax e conectar callbacks.</p>
<p>Na prática você encapsula uma chamada assíncrona num objeto, que você vai “deferir” a execução pra algum ponto no futuro, normalmente depois de ter a oportunidade de configurar esse objeto com callbacks ou esperar outros deferreds ficarem prontos e executar tudo de uma vez só, por exemplo. De qualquer forma é uma forma melhor do que ter hadouken de callbacks.</p>
<p>Retomando, fazer syscalls é caro. Context Switching é caro. Chamar coisas, funções, dentro do próprio processo é rápido e barato. Dentro do seu processo existe seu código transformado em binário. E você tem suas funções lá. Funções que demoram demais pra executar bloqueiam todo o seu processo ou thread. E se fosse possível pausar uma função antes dela acabar, deixar ela de lado, e dar chance de outra função rodar dentro do mesmo processo? Não estou falando de criar 2 threads, estou falando de na mesma thread a função ser pausada, deixar outra executar e daí voltar a executar a primeira. É como se fosse uma thread mas como é tudo dentro do mesmo processo, sem syscalls, sem context switching. É pra isso que serve uma corrotina, pense uma função com múltiplos pontos de suspensão, que você pode continuar ou resumir depois.</p>
<p>Em particular, um caso especial de corrotina se chama Fiber ou Generator. Se você já brincou com classes de Enumerators e Iterators em Python ou Ruby, sabe pra que elas servem. Elas são basicamente funções ou métodos como qualquer outro. Mas de dentro desse método faça de conta que existe um loop gigante, indo até infinito. Significa que esse método vai bloquear tudo e você não vai rodar mais nada pra sempre. Mas de dentro desse loop você pode chamar uma função, normalmente nomeada como yield, que vai devolver o controle pro código anterior que chamou o método. Agora esse código anterior pode executar outra coisa e quando quiser pode chamar o método resume, pra resumir a execução desse método com loop gigante, e ele vai dar yield de novo e assim por diante. Se você lembra de quando eu falei do Windows 3.1, isso seria algo parecido com uma multitarefa cooperativa. Fibers são funções, ou objetos de execução, onde você codifica pontos de suspensão ou pausa da execução. A maioria das linguagens atuais têm algo assim, os nomes vão variar mas se você ouvir corotina, fiber ou generator é basicamente a mesma coisa. Python tem, Ruby tem, Javascript tem, Swift tem, Kotlin tem, todo mundo tem algo assim.</p>
<p>De qualquer forma, pra que servem essas corotinas que pausam e podem ser resumidas se elas não rodam em paralelo? Em princípio elas se parecem com threads: elas estão dentro do processo, portanto tem acesso a todas as estruturas de dados do processo. Porém, como elas não rodam em paralelo, não existe a situação de uma querer escrever em cima dos dados da outra. Você que tem que manualmente pausar e manualmente resumir uma fiber, portanto você tem controle. Por isso elas são mais simples de se trabalhar do que threads. Mas elas não rodam em paralelo, então se parecem mais com as threads de Python ou Ruby, que apesar de terem a capacidade de rodar em paralelo já que mapeia pra threads reais do sistema operacional, existe o GIL que vai bloqueá-las. Mas diferente de threads, não precisamos de chamadas pra Kernel, elas são simples abstrações dentro do seu programa, em user-land.</p>
<p>A existência dessa construção na linguagem facilita a programação de concorrência. ?Eu não disse que a forma rudimentar é uma função recebendo callbacks pra chamar outras funções quando vier o evento que determinado I/O finalizou? Depois inventamos Deferreds que já ajuda. Porém, podemos usar Fibers pra escrever o mesmo código que seria um hadouken de callbacks quase como se fosse programação imperativa tradicional, só que o yield bloqueia até a chamada voltar e daí ele resume daquele ponto. Então veja no exemplo como é um código hadouken
Agora veja o mesmo código usando fibers e a chamada de yield, e você vai ver como de repente do ponto de vista do código tudo ficou mais simples e sequencial. Então só por isso já temos um ganho absurdo em legibilidade e, por consequência, mantenabilidade do código.</p>
<p>Do ponto de vista do código, se somarmos um event loop com I/O assíncrono, conseguimos ter algum nível de concorrência e com fibers conseguimos ter um código que não é tosco. Estamos chegando em algum lugar aqui. Com isso Javascript com Node.js, Python com Twisted, Tornado ou GEvent, Ruby com Eventmachine ou Async, todos tem a possibilidade de usar essa arquitetura de reactors, porque no fundo quem realmente faz a mágica é a Kernel e suas syscalls assíncronas como epoll, kqueue ou IOCP. Porém, todas as bibliotecas que fazem chamadas síncronas, ou seja, do jeito bloqueante, precisam ser reescritas para usar esse recurso de fibers ou generators. Daí você entende porque em Python ou Ruby esse suporte é mais complicado e porque no Node.js é mais fácil: porque Node.js já nasceu com suas bibliotecas sendo criadas pra serem assíncronas.</p>
<p>Isso ajuda em linguagens que tem dificuldades com paralelismo de threads reais. Vamos falar das que tem acesso a threads reais sem o problema de um GIL ou lock global, como Java, ou C#. Pra elas existem Thread Pools. Em vez de ficar criando quinhentas threads a torto e a direito, sem controle, você limita quantas threads vão de fato existir simultaneamente. Como já disse antes uma média de uma thread por core da máquina. Tanto em Java quanto C#. Elas costumam ter alguma coisas como uma classe ThreadPool, literalmente um tanque de threads, e melhor ainda, quando tem outra abstração chamada Task ou Tarefa. A grande vantagem você vê num loop em uma lista gigante, onde você quer processar cada elemento dessa lista em paralelo. Só que se você ficar criando uma thread pra cada elemento, e a lista tiver quinhentos elementos, você vai acabar tento uns quinhentos threads pro sistema gerenciar. Ao passo que se você usar Tasks, que é só uma abstração, e configurar a ThreadPool pra ter só 5 threads, mesmo a lista tendo 500 elementos, você nunca vai ter mais que 5 threads rodando ao mesmo tempo.</p>
<p>Uma Thread Pool funciona como se fosse um load balancer de threads, vamos dizer. Nós usamos o conceito de Pool em tudo que é recurso caro. Por exemplo, bancos de dados nós usamos connection pools. Mesmo se tivermos 500 navegadores simultaneamente conectando no servidor de aplicação, não vamos criar 500 conexões no banco, vamos configurar um pool de conexões pra, digamos 100 conexões, e é só isso que vai existir, os primeiros 100 vão usar essas conexões, quando o primeiro acabar devolve a conexão pro pool e agora o próximo esperando vai pegar e usar e assim sucessivamente. A mesma coisa em ThreadPools. E esse é um dos principais conceitos que você tem que saber em arquitetura de concorrência e paralelismo: pools e filas. É como numa agência de banco. É dia de pagamento, 100 pessoas aparecem na hora do almoço, mas só vai ter 10 caixas. As 10 caixas representam o pool. E assim vamos tendo 10 pessoas sendo atendidas simultaneamente e as outras esperam na fila. É assim que o scheduler de threads funciona também. E é assim que podemos ter algum controle sobre as threads do sistema operacional.</p>
<p>No mundo .NET você tem classes como Parallel e Task que abstraem esses conceitos. Em Ruby temos também uma blblioteca chamada Parallel que vai abstrair pools de forks ou pools de threads. No mundo de Mac e BSD existe o Grand Central Dispatch e seus DispatchQueue. Enfim, cada linguagem ou sistema operacional vai oferecer alguma coisa que tem essa arquitetura. Alguma combinação de queues ou filas e pools. Preste atenção: você raramente quer usar Threads reais diretamente, especialmente em grandes quantidades. Se precisar de threads sempre use Pools, Queues e Tasks.</p>
<p>Esse conceito também se estende pra computação distribuída. Até agora sempre falamos de um código que, na hora que precisa, carrega alguma forma de executar uma tarefa em paralelo. Ou um fork pra gerar um novo processo. Ou criando uma nova Thread. Ou agora criando Pools. Mas existe a opção de já ter essa segunda entidade pré-carregada e esperando. Um segundo processo, ou uma thread ou algo assim.</p>
<p>Em Java sempre tivemos no Enterprise Edition os JMS ou Java Message Service. No mundo Ruby on Rails usamos coisas como Sidekick, a aplicação conecta a uma fila representada num banco de dados Redis e existe um ou mais processos que ficam pré-carregados e ouvindo nessa mesma fila. Chamamos esses processos secundários de Workers. Temos isso em quase toda linguagem. Uma fila e vários workers, e como é um número limitado de workers é como se fosse um Pool também.</p>
<p>No mundo .NET temos opções como o projeto Hangfire. No mundo Python temos projetos como Celery ou RQ. No mundo Node.js temos coisas como o Kue. No mundo Java temos Message Queues no JEE ou Background Jobs no Spring. O conceito é sempre o mesmo: algum tipo de fila como RabbitMQ, Redis ou qualquer coisa assim, e diversos workers consumindo dessas filas. É uma solução de paralelismo que funciona tanto na mesma máquina quanto em diferentes máquinas, representando um tipo de computação distribuída. Outro nome que você vai ouvir associado a esses mesmos conceitos é Background Jobs.</p>
<p>Esse tipo de solução resolve a grande maioria dos problemas reais que temos hoje. Mesmo numa aplicação mono-thread, que tenha um reactor, ainda temos operações que custam tempo e processamento, que eu já expliquei que vão bloquear o event loop. Em vez de executar isso no processo principal, podemos enviar uma mensagem a uma fila e deixar outro processo, ou worker, fazer esse trabalho pesado, e assim deixar o reactor principal mais leve e mais rápido.</p>
<p>Mas ainda assim gostaríamos de alguma coisa mais eficiente dentro de um mesmo processo. E até agora não chegamos lá ainda. Mas se você prestou atenção aos episódios anteriores, existe sim uma construção pra definir concorrência em sistemas operacionais e hardware que não tem mais do que uma thread real. Uma green thread. Ou seja, uma thread que só existe em User land e não em kernel space. É o que o protótipo do Java original tinha no começo quando foi desenhado pra set-top-boxes, tipo Tivo como eu expliquei em outro episódio. Um conceito de hardware barato do começo dos anos 90 que não rodaria nada muito mais potente que um DOS. Se quiséssemos programar concorrência, não haveria threads reais, mas mesmo assim podemos programar concorrência com green threads, só que elas nunca vão rodar em paralelo. Lembram? Uma coisa é ter concorrência, outra coisa é ter paralelismo. Ter paralismo implica ter concorrência, mas não o oposto.</p>
<p>Corotinas, fibers ou generators que expliquei acima, seriam mecanismos para chegar em green-threads: funções com pontos de suspensão, onde podemos gerenciar várias delas ao mesmo tempo, e ir executando um pouco de cada uma de cada vez. À primeira vista, hoje que temos máquinas multi-core com múltiplas threads paralelas reais, pra que serviriam green-threads? Pra muita coisa. Eu já repeti várias vezes e vou repetir de novo: threads custam memória, talvez 1 MB, tem context switching caro, executado pelo scheduler da kernel, tem syscalls que precisam pular de user-land pra kernel space. Green threads são baratas, dependendo da linguagem pode custar talvez na ordem de 2 quilobytes, ou seja umas 500 vezes mais leve que uma thread. Elas estão dentro do mesmo processo, em user land, e não dependem de syscalls nem de context switching da kernel. Ou seja, comparado com threads, elas são realmente muito mais baratas. Mas tem um problema: só com o que sabemos até agora, elas não rodam em paralelo.</p>
<p>Aí que você se engana. E se, usássemos o conceito de thread pools e fizéssemos um código que pegasse milhares de green-threads e mandássemos executar num pool de threads reais? Basta construirmos um scheduler em user-land que se encarregaria de fazer esse load balancing de green-threads entre threads num pool. E muito bem chegamos à base de um Scala, Clojure, Go ou Erlang e, por consequencia, Elixir.</p>
<p>Erlang, nos anos 80, fez o seguinte: quando sua VM Beam sobe, ele sequestra os recursos da máquina pra ele: o I/O, a memória e principalmente as threads reais do sistema operacional. Ele roda um scheduler em user land pra cada thread real. As funções em Erlang não são só fibers, elas são corotinas, e a VM tem o poder de arbitrariamente pausar uma função quando bem entender, ou seja, ela não depende que você tenha programado um yield na função pra devolver o controle, ela vai pausar quer você queira ou não, exatamente como a Kernel do Linux faz com seus programas. Ou seja, o Erlang implementa o equivalente a um scheduler preemptivo de green threads em user land, e em vez de fibers ela tem corotinas de verdade.</p>
<p>Mas o Erlang tem algo mais importante ainda. Um dos problemas de uma thread é que se ela tem problemas, exceções, erros e outros bugs, ela pode corromper toda a memória do processo. Então basta uma thread fazer bobagem e seu processo inteiro pode crashear. Pra evitar isso o Erlang resolveu que cada uma dessas suas corotinas não compartilhasse nenhuma memória com outras corotinas. Na verdade, pra dificultar nosso vocabulário, dentro do Erlang ele chama essas corotinas que estão executando de “processos”, não confunda com processos de verdade de um Linux, apesar do comportamento ser similar. Existem funções normais como de qualquer outra linguagem, mas existem esses processos de erlang. Como esses processos não enxergam memória de outros processos, se você quiser fazer um processo se comunicar com outro temos o equivalente a Unix Sockets se fossem processos de Linux, canais bi-direcionais por onde eu posso enviar mensagens.</p>
<p>Essas mensagens são estruturas de dados, de novo, não compartilhadas. A VM do Erlang, assim como um OS Linux, aloca espaços de memória protegidos pra cada processo. Mas diferente do kernel Linux, que aloca 1 MB pras estruturas de um processo ou thread, o Erlang não precisa alocar mais que 2 quilobytes pra cada processo. O processo vai recebendo mensagens num mailbox, um tipo de fila e escolhe o que fazer com essas mensagens quando quiser. Quando um processo termina ou crasheia, existe um garbage collector pra cada processo individualmente. E isso pra mim é o que diferencia Erlang de todo o resto, um processo, que é um green-thread, com bug não afeta e nunca corrompe o resto do sistema já que ele não compartilha nada.</p>
<p>Eles não compartilham ponteiros nem nada e por isso nunca tem a capacidade de desestabilizar o sistema. Por isso eu costumo dizer que a VM Beam do Erlang é quase como se fosse um mini-Linux. Pra ficar mais similar ainda, os Linux tem um processo master que dependendo da distro pode ser um initd ou systemd ou outro que sobe daemons, que já expliquei antes, são processos especiais, serviços do sistema. No Erlang temos a mesma coisas só que se chamam Supervisors. Eles se encarregam de quando você subir sua aplicação, no boot da VM do Erlang, vai subir os supervisors que vão iniciar os processos necessários e garantir que se um deles crashear ele vai subir um novo processo no lugar.</p>
<p>Eu expliquei em 2 minutos o Erlang, mas pense em Erlang mais como um Java: uma máquina virtual que te oferece diversos serviços, incluindo coisas que o Java ou .NET não tem: schedulers e processos. Esses processos são green-threads. Tudo roda em user-land, e por isso é extremamente barato. É tão barato de fato que podemos fazer o que eu expliquei que fazíamos antigamente: toda nova conexão de rede antes a gente gerava uma thread real pra lidar com ela. Mas com o C10K descobrimos como é caro e não escala ficar criando threads reais o tempo todo pra toda conexão. 10 mil threads consumiria 10 gigabytes de RAM. Mas 10 mil processos Erlang consome só 19 Megabytes. Portanto, como essa green-thread é ordens de grandeza mais barato, podemos voltar a criar uma green-thread por conexão se quisermos. Entenderam a diferença? Green-threads são ordens de grandeza mais eficientes que uma thread real, e se a máquina virtual tem seu próprio scheduler pra gerenciar a thread-real pra rodar as green-threads temos paralelismo de verdade.</p>
<p>No Java normal não temos essas construções, mas outras linguagens por cima do Java como Scala e Clojure implementam estruturas similares de green-threads e schedulers. Em particular o Scala introduziu o framework Akka por volta de 2009, que implementa a arquitetura de Actors. Actors, é como eu chamo essa função especial no Erlang, os processos, que tem características de corotinas com múltiplos pontos de suspensão, com mailboxes que servem como filas de comunicação, e onde as mensagens trocadas entre processos não compartilham memória, nunca trafegam coisas como ponteiros, mantendo as green-threads isoladas entre si. Isso é um Actor, basicamente. Por isso tanto Erlang quanto Scala compartilham de arquiteturas similares, apesar das linguagens em si serem bem diferentes. O Jonas Bonér, criador do Akka, se inspirou diretamente no Erlang pra fazer o framework Akka do Scala, por isso eles tem conceitos similares.</p>
<p>Lembram quando eu expliquei que os schedulers de thread de Linux e o próprio suporte de pthreads eram ruins no Linux e que só depois de 2007, na kernel 2.6, que ganhamos o NPTL e o CFS do Igor Molnár? Eu expliquei que o sistema operacional mapeia uma estrutura de thread pra cada core real do hardware. O tal modelo um pra um. Mas se você adicionar um scheduler e múltiplos green-threads em user land, voltamos ao modelo M pra N que eu disse que a IBM tentou implementar no Linux com o projeto NGPT. Lembram que eu falei pra vocês guardarem essa informação? Estamos revisitando o tema agora.</p>
<p>O Go faz mais ou menos a mesma coisa. O que o Erlang chama de processo, em Go se chama goroutine. Só que eu diria que as goroutines são mais baixo nível do que um Actor de Scala. Num Erlang, assim como num Linux, cada processo tem um PID ou Process ID, um número que identifica unicamente cada processo rodando. Esse PID serve como um e-mail, e um processo pode mandar mensagens pra outro processo através desse PID. O Erlang é tão versátil que podemos mandar mensagens pra um PID de um processo em outra máquina.</p>
<p>O Go tem uma outra abstração: as channels, literalmente canais. Duas gorotinas podem compartilhar uma mesma channel. Uma gorotina escreve num channel e outra gotina pode ficar escutando do mesmo channel. Lembram de algo similar que expliquei hoje? Na prática não me parece nada muito diferente de um named pipe entre processos Unix, um canal unidirecional bloqueante. E eu disse que gorotinas e channels são mais baixo nível que Actors de Erlang ou Scala porque eles permitem trafegar qualquer coisa, incluindo ponteiros de memória, ou seja, eles compartilham memória entre si e, portanto, tem os mesmos problemas que programar com threads reais: se você escolher trafegar ponteiros, vai precisar lidar com mutexes e locks e os temidos bugs de race condition e deadlocks.</p>
<p>Diferente de Erlang ou Java, o Go não é uma máquina virtual, cheia de serviços. A filosofia e os usos são diferentes. Ele tem um runtime por causa do controle das gorotinas que podem rodar em paralelo. Diferente do scheduler de Erlang que é preemptivo, o scheduler de Go é cooperativo. Ele espera determinados eventos numa gorotina como syscalls pra receber o controle de volta e dar a vez pra outra gorotina. É menos sofisticado do que Erlang nesse caso mas é mais leve e mais previsível também. E obviamente, por causa disso ele é automaticamente mais poderoso do que Node.js e mais simples de programar aplicações complexas. Assim como Erlang ou Scala, um reactor é desnecessário, porque eles tem schedulers que podem pausar uma corotina quando alguma coisa como I/O bloqueante é executado. Na minha opinião, schedulers em user-land pra corotinas são melhores do que reactors. Reactors são interessantes em linguagens que não tem como ter schedulers em user-land. A premissa pra isso é acesso irrestrito às threads reais, sem locks globais que Python ou Ruby tem.</p>
<p>Do ponto de vista puramente dos interpretadores ou máquinas virtuais. Temos PHP, Python, Ruby, Perl e Javascript que são todos interpretadores. Alguns tem acesso a threads reais, mas na prática, todos são essencialmente single-threaded, com locks globais que chamamos de GILs por conta das antigas extensões em C. Em todos eles, a opção de um Reactor com a ajuda de Fibers pra não complicar a programação, é a melhor solução pra concorrência. Erlang e Java são máquinas virtuais, no caso de Java significa que Scala, Clojure ou mesmo Kotlin, todos rodam sobre a mesma máquina virtual. Essas VMs são excepcionais trabalhos de engenharia e foram feitas pra sequestrar todos os recursos da máquina. O melhor caso de uso é seu programa e a VM rodarem sozinhas na máquina e elas gerenciarem tudo. E mais do que isso: o melhor caso de uso é que uma vez iniciadas elas não fiquem rebootando toda hora. Guardem essa informação pro episódio de devops.</p>
<p>Erlang, na minha opinião, é quem tem as melhores abstrações de concorrência, e por consequência Elixir também já que ele basicamente gera bytecodes pra mesma VM, usando os mesmos frameworks. Em Java, Scala e depois Clojure e Kotlin adicionaram abstrações de linguagem e bibliotecas pra facilitar concorrência, inclusive no caso de Scala e Clojure implementando schedulers user-land e passando a adotar o conceito de green-threads, que o Java nativo não tem. O C# veio copiando cada uma dessas coisas de outras linguagens como Fibers, ThreadPools e tudo mais, mas que eu me lembre ele também não tem schedulers em user-land nem green-threads. Kotlin, Swift, C# tem sim classes chamadas schedulers que não é a mesma coisa que eu falei de Scala ou Go. Eles não são serviços do runtime, eles são classes pra agendar coisas pro futuro, como um cron num Linux, onde vc diz pra determinada tarefa rodar numa thread num determinado horário ou numa determinada frequência. Os nomes confundem, eu sei, mas schedulers de Kotlin, por exemplo, é como se fosse um cron.</p>
<p>O Go é uma linguagem que compila binário nativo, assim como Rust ou C. Mas o Go possui um runtime, pense um pedaço grande de código que gruda no seu pra gerar o binariozão final. Ele tem um scheduler real cooperativo e oferece green-threads com coordenação via channels, que como eu disse, parece com named pipes como eu expliquei no começo do episódio. Rust e outras linguagens como Crystal ainda Não tem o mesmo nível de concorrência e paralelismo. Mesmo em Rust o suporte a I/O assíncrono demorou pra chegar. Crystal está agora ganhando suporte experimental a rodar fibers em paralelo, mas nenhum dos dois possui schedulers em user-land que vem pré-embutido no runtime da linguagem.</p>
<p>Se não ficou claro, Javascript não tem paralelismo. Ele é a mais simples de todas as linguagens, e muito do investimento em engenharia no Google se focou em tornar a V8 muito boa em cuspir código nativo, portanto ele é um Just In Time Compiler, um JIT muito bom. Ele não tem sintaxe complicada, não tem bibliotecas padrão pra manter, e obviamente não oferece scheduler user-land também. Não tem um GIL como Python ou Ruby, mas também não tem acesso a threads reais. Sua única forma de concorrência são generators ou fibers, em multitarefa cooperativa, e na falta de um scheduler, essa multitarefa é controlada num event loop reactor. No máximo dá pra fazer fork pra outros processos. Porém, Javascript troca performance por uso de memória, então Copy on Write do Linux Não ajuda muito porque ele faz JIT, então muita coisa não pode ser reaproveitada entre processos. Por isso eu tendo a dizer que apesar de ser rápido, é uma das linguagens mais rudimentares que temos.</p>
<p>Eu ia falar sobre a parte da programação, mas como já tá comprido pra caramba de novo vou só resumir. Eu expliquei até agora sobre como as coisas funcionam por baixo. Agora como tudo isso fica no seu código? Se lidarmos com threads reais, já expliquei que precisamos ficar programando mutexes pra lá e pra cá. Em linguagens antigas como Java ou C#, que expõe Threads reais, você ainda precisa se preocupar com isso. Em Go também você precisa se preocupar com isso, porque ele transporta ponteiros nos channels entre gorotinas. Ou seja, o oposto de um Erlang ou Elixir que não compartilha nada entre seus processos. Portanto ficou na sua mão como desenvolvedor se virar com sincronização e coordenação. Experimente criar um deadlock no seu channel de Go: seu programa crasheia. É um dos motivos de porque eu fico decepcionado com Go em 2019 que já poderia ter oferecido abstrações melhores direto na linguagem. Mas é uma opinião pessoal, o mesmo problema acontece num C ou C++ onde eu tenho acesso a tudo, incluindo ponteiros. Esconder ponteiros é uma decisão de design da linguagem. Mas assim como GOTO desapareceu, Null é considerado ruim hoje em dia, Ponteiros também já haviam sido aposentados quando Java foi criado, não é nenhuma novidade.</p>
<p>De qualquer forma, Node.js no começo, por 2010, tornou famoso o estilo porco de programação com callbacks. Twisted e JQuery tornaram famosos a abstração de Deffereds. E Scala, não lembro se foi o primeiro, mas foi um dos que ajudou a popularizar o termo Future. Não deixa de ser uma pequena evolução sobre Deferreds. Na prática é mais uma forma de encapsular uma execução futura num objeto, um tipo de placeholder. Em vez de fazer sua variável receber direto o resultado de uma chamada assíncrona ou mesmo uma chamada externa tipo uma chamada HTTP, que não se tem certeza se vai completar, ou uma query num banco, você faz a variavel receber um Future e o Future encapsula essa chamada incerta. Quando o Future executar e resolver, ele é substituído pelo resultado.</p>
<p>Ninguém mais usa os nomes Deferred ou Futures ou mesmo Delay, o próximo passo nessa evolução são Promises, em particular o famoso spec A+. Se você está em Javascript faz alguns anos, já deve estar de saco cheio de ouvir falar de Promises. Dois ou três anos atrás eu dizia que a ironia de Promises é que eles permaneciam sendo promessas. Enfim, na prática, Promise é como eu já falei antes de Deferred e Futures, um objeto que encapsula alguma chamada incerta. Esse objeto é encadeável e customizável e costuma ter métodos como then e catch, que é onde você adiciona callbacks, como o addCallback do Deferred de Twisted. Java 8 tem CompletableFuture, frameworks como Guava tem SettableFuture. Javascript tem Promise/A+.</p>
<p>Deffereds, Delays, Futures, Promises, são termos que acabam sendo usados um no lugar do outro, mas hoje em dia as pessoas falam mais de Promises. Sobre os objetos de Promises podemos adicionar o que chamamos de syntactic sugar, ou literalmente um açúcar na sintaxe da linguagem pra não ter que lidar com objetos de Promise diretamente nem essas configurações via then ou catch. Se não me engano a Microsoft desenvolveu a sintaxe de async/await pro C# e várias outras linguagens implementaram algo similar.</p>
<p>Voltando um pouco, em linguagens que tem Threads costuma também existir a função de join. Por exemplo, você pode criar um array ou lista com 10 Threads. Ao criar todas elas, o código pai que está criando pode chamar Join em todas elas. Assim o programa principal vai ficar bloqueado até todas elas executarem e retornarem. Daí ele continua a execução com o resultado de todas as threads, que garantidamente já acabaram de rodar. Async e Await é quase a mesma coisa, funções marcadas como async costumam devolver um objeto Promise e o await é como se fosse um join numa thread, ou um yield num Fiber, é tipo um join numa promise, esperando que ela resolva pra depois continuar.</p>
<p>A diferença é que async/await é uma semântica que pode ser usada em threads, green-threads ou corrotinas ou fibers. Então é como se fosse Join pra coisas que não são threads. Todo programa que você escreve são trechos de código que chamam outros trechos de código, uma função chamando outra. Pra explicar um exemplo, vamos chamar a primeira função de Maria e a segunda função de Joao. Maria chama Joao, Joao executa, e devolve controle pra Maria. Se Joao for uma Fiber, Maria chama Joao, Joao começa a executar e dá yield, suspende e devolve controle pra Maria. Agora Maria pode fazer outra coisa e, se quiser, pedir pro Joao resumir de onde parou.</p>
<p>Com Promises é assim: Maria chama Joao, Joao não faz nada e devolve uma promessa. Maria pode escolher quando cobrar a promessa. Daí ela cobra, e Joao executa o prometido e quando termina “resolve” devolvendo o resultado. Agora Maria pode escolher o que fazer com o resultado. Ela tem duas opções, ou usar a sintaxe padrão de Promises e dizer ao Joao: Joao quando você terminar chama a Camila. Ou, a Maria pode cobrar a promessa do Joao, esperar ele terminar que é o await, e quando terminar ela mesma pode chamar a Camila. Entenderam, é uma questão de estilo de código, ambos vão chegar no mesmo resultado, mas hoje em dia se tornou consenso que se precisar fazer uma chamada externa, por exemplo uma requisição de rede, que não sabemos quando vai ficar pronto ou mesmo se vai completar, então englobamos num Promise. Daí escolhemos se esperamos já ele terminar ou se só atrelamos nele um callback e vamos fazer outra coisa.</p>
<p>Processos, threads, user-land e kernel-space, context switching, schedulers de user-land, corrotinas, fibers, generators, são construções do sistema operacional, interpretador, runtime ou máquina virtual pra modelar e executar concorrência e paralelismo. Recursos das linguagens como filas, mailboxes e channels, promises e async/await são recursos das linguagens pra facilitar a programação de concorrência e paralelismo.</p>
<p>Finalmente, chegamos ao final. Espero que tenham aprendido o suficiente pra começar a entender como as diferentes linguagens lidam com concorrência e paralelismo, o que de fato acontece por baixo dos panos, e não misturem o que uma linguagem pode ou não pode fazer. Na prática, hoje em dia, todas as linguagens tem recursos muito bons. Alguns servem pra coisas que os outros não são tão bons, então nenhuma das linguagens que falei aqui é obrigatoriamente melhor do que a outra. Não adianta ser só rápido. Não adianta só ter concorrência massiva. Uma boa linguagem, primeiro, precisa de um ecossistema que gera resultados com eficiência. Quanto menos desperdício, quanto mais resultados, pra determinado caso de uso, melhor.</p>
<p>Estamos já faz muito tempo nesse assunto de back-end e o assunto de concorrência se esticou bastante. Ainda estou em dúvida se semana que vem ainda adiciono mais um tema, que é relacionado a gerenciamento de memória, ou se já vou direto pro assunto final da série: devops. Vou pensar ainda. Se ficou com dúvidas não deixe de mandar nos comentários abaixo, se curtiu mande um joinha, compartilhe com seus amigos, não deixe de assinar o canal e cilcar no sininho pra não perder os próximos episódios. A gente se vê semana que vem, até mais.</p>
]]></content:encoded><category>sistemas-distribuidos</category><category>linguagens-de-programacao</category><category>backend</category><category>akitando</category></item><item><title>[Akitando] #43 - Concorrência e Paralelismo (Parte 1) | Entendendo Back-End para Iniciantes (Parte 3)</title><link>https://akitaonrails.github.io/2019/03/13/akitando-43-concorrencia-e-paralelismo-parte-1-entendendo-back-end-para-iniciantes-parte-3/</link><guid isPermaLink="true">https://akitaonrails.github.io/2019/03/13/akitando-43-concorrencia-e-paralelismo-parte-1-entendendo-back-end-para-iniciantes-parte-3/</guid><pubDate>Wed, 13 Mar 2019 17:00:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/cx1ULv4wYxM"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;De volta à série Começando aos 40, estamos já no oitavo episódio! E ela é a Parte 3 do tema de Back-end, mas desta vez vou precisar me alongar mais explicando conceitos antes de retornar às ferramentas.&lt;/p&gt;
&lt;p&gt;Concorrência e Paralelismo é algo que todo iniciante hoje em dia já esbarra logo cedo. Nós vivemos num mundo que é naturalmente paralelo e concorrente. Já estamos no ponto onde nos definimos como &amp;ldquo;multi-tarefas&amp;rdquo;.&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/cx1ULv4wYxM"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>De volta à série Começando aos 40, estamos já no oitavo episódio! E ela é a Parte 3 do tema de Back-end, mas desta vez vou precisar me alongar mais explicando conceitos antes de retornar às ferramentas.</p>
<p>Concorrência e Paralelismo é algo que todo iniciante hoje em dia já esbarra logo cedo. Nós vivemos num mundo que é naturalmente paralelo e concorrente. Já estamos no ponto onde nos definimos como &ldquo;multi-tarefas&rdquo;.</p>
<p>Mesmo assim, ainda existe mais superstição e &ldquo;misticismo&rdquo; do que real noção do que esse conceito realmente significa. E ao contrário do que possa parecer, na realidade o básico não é tão complicado assim.</p>
<p>Preste bastante atenção na explicação de hoje, porque isso vai ser base pra todo o resto que vou explicar até o fim desta série.</p>
<p>Se você já conhece os detalhes do que estou explicando, obviamente pra caber num vídeo para iniciantes, estou simplificando BASTANTE muitos conceitos pra ilustrar. Infelizmente não cabe tudo num episódio só, mas não deixem de comentar se achar que faltou alguma coisa muito importante!</p>
<p>Links:</p>
<ul>
<li>The Linux Scheduler: a Decade of Wasted Cores (<a href="https://blog.acolyer.org/2016/04/26/the-linux-scheduler-a-decade-of-wasted-cores/"target="_blank" rel="noopener">https://blog.acolyer.org/2016/04/26/the-linux-scheduler-a-decade-of-wasted-cores/</a>)</li>
<li>The C10K problem (<a href="http://www.kegel.com/c10k.html"target="_blank" rel="noopener">http://www.kegel.com/c10k.html</a>)</li>
</ul>
<p>=== Script</p>
<p>Olá pessoal, Fabio Akita</p>
<p>Vamos voltar à Parte 3 do tema de Back-end. Recapitulando já expliquei os conceitos básicos do computador e do sistema operacional, sobre processos e threads. Expliquei sobre compilação e bibliotecas estáticas e dinâmicas. Já expliquei sobre máquinas virtuais e interpretadores. Demos uma rápida passada em características de algumas linguagens, a história do Java, um pouco do lado .NET, até dei uma tangente na história inicial de desenvolvimento mobile e mais um pouco sobre licenças de software.</p>
<p>Estamos já no oitavo episódio da série Começando aos 40. E hoje acho que é hora de começar a tratar de um tema que é muito discutido mas eu acho que os iniciantes tem dificuldade pra visualizar na cabeça. Concorrência e Paralelismo. Mas antes entrarmos nos assuntos mais óbvios como Go ou Elixir ou Node.js precisamos voltar na história como sempre pra que todos finalmente comecem a entender esse assunto de verdade!</p>
<p>Infelizmente explicar concorrência e paralelismo só falando é muito difícil. Vocês ainda vão precisar praticar programando centenas de vezes coisas diferentes pra começar a entender de verdade como as coisas funcionam. Eu espero que a explicação que eu dê agora pelo menos ajude um pouco vocês terem uma imagem na cabeça do que esperar quando realmente colocarem as mãos no metal. Vamos lá!</p>
<p>(&hellip;)</p>
<p>Hoje em dia todo mundo tem um computador potente. Mesmo o mais fraco Intel i3 é dual core com 3 Ghz ou mais. E se você tiver a bufunfa pra comprar os novos Intel i9 ou AMD Threadripper com 8 cores e mais de 4 Ghz, você tem mais poder de computação do que os mais poderosos computadores dos anos 60, combinados. E se falarmos de processadores de servidor, como os Intel Xeon E7 temos até 24 cores. E vocês sabem que a Intel tem a tal tecnologia hyperthreading que faz você enxergar 2 threads simultâneas num mesmo core, então esse Xeon E7 de 24 cores pode ter até 48 threads de execução. Isso é bastante paralelismo. Até mesmo seu celular Android, com o chipset Qualcomm 845, é nada menos que octa-core com 2.8 Ghz cada.</p>
<p>E isso porque não estamos falando de GPUs que tem propósitos diferentes de um CPU, mas possuem cores que processam em paralelo também, no caso da NVIDIA o que chamamos de CUDA cores. E na nova RTX 2080 temos nada mais, nada menos, que 2.944 CUDA cores. Sendo que em placas mães melhores podemos colocar 2 placas dessas rodando em paralelo e poder ter acesso a quase 6 mil CUDA cores ao mesmo tempo. Isso sim é processamento paralelo massivo.</p>
<p>Então, do seu celular, a desktop, servidores, estamos claramente num mundo massivamente paralelo. Mas não foi sempre assim. Considere que acesso a esse mundo paralelo só começou de verdade pra nós programadores a coisa de pouco mais de uma década. Pra entender como tudo mudou, vou resumir como foi do fim dos anos 50 até agora.</p>
<p>Nos anos 50 talvez vocês já tenham ouvido falar do meme que a IBM dizia que o mundo não precisa de mais do que cinco computadores. Eles estavam falando do IBM 701. E se você não sabia disso, essa frase foi tirada de contexto e é fake news, pra variar. A frase em contexto foi numa reunião de acionistas que dizia que a IBM tinha planos de vender talvez cinco IBM 701 mas voltaram com pedidos pra 19. Estamos falando de uma máquina que, se fosse alugar na época, não custaria menos que 15 mil dólares, em dólares dos anos 50. E a potência? Era uma máquina de 18-bits. Lembrando que seu celular hoje é 64-bits. E essa máquina vinha com nada menos que 1 quilobyte de memória e podia suportar um máximo de 9 quilobytes. E ainda foi a primeira que vinha com … gravador de fita, que podia gravar nada menos que 1.2 megabytes a uma velocidade estrondosa de 5.6 kilobytes por segundo. Lembrando que sua placa de rede ethernet porcaria trafega dados a pelo menos 1 gigabits por segundo, e isso é considerado lento hoje em dia dado que existem placas de 10 gigabits.</p>
<p>Então você imagina que numa máquina cara dessas, basicamente uma calculadora glorificada, você gostaria de usá-la da forma mais eficiente possível. Era uma época onde a linguagem mais avançada era Fortran pra fazer cálculos. E onde você carregava programas com cartões perfurados, numa máquina que conseguia ler no máximo 150 cartões por minuto e perfurar no máximo 100 cartões por minuto. Então a forma mais eficiente de usar essa máquina era os programadores serem extremamente cuidadosos pra escrever programas de cálculo muito precisos e sem erros, que chamamos de jobs, e organizar esses jobs em batches ou grupos de programas similares, e deixar numa fila pra ir processando dia e noite. Foi a época dos jobs em batch. Se tivesse um erro no seu programa, você só ia saber no dia seguinte e ter que repetir todo o processo.</p>
<p>Então dos anos 50 pros anos 60, alguns grandes nomes que já falei antes como John Backus, criador do Fortran, ou John McCarthy que é considerado o pai da inteligência artificial e o criador do garbage collector do Lisp, começaram a trabalhar um conceito para aumentar a eficiência dos programadores. Em vez de terem que colocar seus jobs numa fila e ficar só esperando de braços cruzados, e se fosse possível usar as ineficiências do computador, como o tempo que ele fica lendo ou escrevendo nas fitas e outras pausas, pra permitir que múltiplos programadores pudessem trabalhar concorrentemente no mesmo computador?</p>
<p>O conceito básico era que fosse possível salvar o contexto do ambiente de um programador e trocar pelo contexto de outro programador. Entenda contexto como o que define o ambiente de trabalho do programador, o que está na memória basicamente. Se você trocar muito rápido entre um contexto e outro, ou seja fazer switch, para os programadores “parece” que ele está sozinho na máquina, mas na verdade a máquina está gerenciando entre múltiplos contextos. A máquina oferece um pedaço de tempo, ou time-slice, para cada ambiente e vai dando a vez pra outro ambiente, sucessivamente. Esse é o conceito de time-sharing baseado em context switching.</p>
<p>Esse conceito nasceu na época dos IBM 704 e IBM 709 e foi incorporado aos mainframes dos anos 60. O conceito de time-sharing existiu mais no mundo corporativo, em mainframes e mini-computadores dos anos 60 até 70. Nos anos 80 principalmente começou a aparecer nos UNIX usados em workstations. E só nos anos 90 começou a de fato a aparecer no mundo dos desktops. Se você usou micro-computadores nos anos 70 e 80, lidou com micros muito simples, com CPUs como o Z80 ou Motorola 68000 ou mesmo Intel 8088. Todos CPUs de um único core, que só sabiam executar uma instrução de cada vez a cada giro do clock.</p>
<p>Se você já teve um Sinclair, MSX, Commodore 64, Apple II ou outras máquinas 8 bits ou 16 bits da década de 80, você se viu na mesma situação dos computadores dos anos 50: só conseguir executar uma tarefa ou um job de cada vez. Num MS-DOS você podia executar um programa, mas pra executar outro precisava sair do primeiro e só depois carregar o segundo. Ou usar um truque de interrupção, a famosa INT 21H que era o recurso de Terminate and Stay Resident ou TSR. De uma forma rudimentar você podia carregar um segundo programa, chamar essa interrupção pra devolver controle pro DOS mas deixar residente em memória de forma que podia ser chamado de volta. Era como utilitários pequenos com o Borland Sidekick funcionavam na época. E isso meio que simulava um tipo de multi-tarefa.</p>
<p>Foi só com o Windows 3.1 que a maioria das pessoas realmente experimentou mais esse conceito de multi-tarefa, mas no caso era multi-tarefa cooperativa. Quando falamos em multi-tarefa num computador com CPU de um único core, pensa numa mesa de escritório sendo dividida entre mais de uma pessoa. A primeira pessoa chega, coloca as coisas dela na mesa e começa a trabalhar. Pra outra pessoa usar, a primeira precisa tirar todas as coisas dela da mesa e deixar a outra colocar as coisas dela. As coisas da primeira pessoa é o tal contexto e a troca é o switching. É o mesmo conceito de time-sharing que falei antes. Mas se a segunda pessoa se recusar a sair da mesa, ninguém mais consegue usar a mesa, então ela precisa cooperar pra todo mundo poder usar. Esse é o conceito de multi-tarefa cooperativa, que é o que tinha no Windows 3.1. Se um programa fizesse alguma coisa que bloqueasse o sistema como imprimir, todos os outros programas ficavam impedidos de ter sua vez pra rodar.</p>
<p>Isso veio a mudar em sistemas operacionais 32-bits como OS/2 ou Windows 95, Windows NT e também nas distribuições Linux que começaram a aparecer do começo pro meio da década de 90. Nesse caso temos o que já expliquei antes: cada processo era iniciado pelo sistema operacional num espaço de memória protegido, ou seja, onde um programa não consegue escrever em cima da memória do outro. Além disso agora não é o programa que tem que cooperar pros outros poderem rodar. Na metáfora da mesa, imagine que agora tenha um supervisor, que tira a pessoa da mesa pra deixar a outra usar mesmo se a pessoa não quiser sair naquele momento. Esse supervisor é que chamamos de scheduler do sistema operacional. Ele aloca um pedaço de tempo, ou time-slice pra cada programa e interrompe pra deixar outro rodar. A forma de fazer isso é pegar o que está na tal mesa, separar do lado e colocar as coisas da outra pessoa na mesa. E quando der o tempo, ele tira as coisas da mesa e devolve o que tava antes e vai fazendo esse switching de contexto. Isso é multi-tarefa preemptiva.</p>
<p>Isso é feito no nível do processo, que na metáfora é o que está executando na tal mesa naquele determinado momento. Mas digamos que quiséssemos ter tarefas em paralelo na mesma mesa ao mesmo tempo. Por definição, cada processo tem uma única thread de execução, pense numa thread como sendo uma tarefa sendo executada na mesa. Mas podemos ter multi-thread que seria o equivalente a ter 2 pessoas usando a mesma mesa e os mesmos materiais na mesa ao mesmo tempo. Pra isso acontecer o tal supervisor faz a mesma coisa: ele força uma das pessoas a parar de usar o material da mesa pra deixar a outra pessoa sentada do lado usar o mesmo material.</p>
<p>Multi-thread é mais ou menos isso: duas pessoas começando com o mesmo material ao mesmo tempo. Cada pessoa pode até usar seu próprio material, mas ela tem acesso ao material todo que está na mesa, que na metáfora é o espaço de memória do processo. Um processo pode ter uma ou mais threads. Num processador com um único core, não é possível que as duas pessoas trabalhem exatamente ao mesmo tempo, porque só tem um único espaço na mesa, então a única forma de haver concorrência é o tal supervisor ficar fazendo uma das pessoas pausar o trabalho pra dar um tempo pra outra pessoa continuar o trabalho dela.</p>
<p>Quando falei de um processador moderno como o Intel i3, ela tem 2 cores. Pense em 2 cores como duas mesas. Nesse caso cada thread ou cada pessoa realmente pode trabalhar verdadeiramente em paralelo. Mas nos anos 90 o comum era um processador 486 ou Pentium de um único core. Só servidores tinham processadores com dois ou mais cores. Então você tinha multi-tarefa preemptiva que fica pausando threads pra outra poder rodar, e agora você tem multi-processamento paralelo onde cada thread roda separadamente em um processador separado.</p>
<p>Quando são dois processos independentes, eles podem rodar 100% em paralelo. Se não estiverem usando nada que é compartilhado pelos dois como um arquivo ou outro recurso de hardware como impressora ou rede. Se um deles estiver usando a impressora, obviamente o segundo processo é obrigado a esperar ou fazer outra coisa enquanto espera. Mesma coisa se forem 2 threads dentro do mesmo processo. Seria o equivalente às duas pessoas uma em cada mesa mas onde ambos dependem do mesmo material pra trabalhar. E é nessa situação que multi-threading começa a ficar complicado.</p>
<p>Se ambas as pessoas quiserem escrever no mesmo pedaço compartilhado de papel ao mesmo tempo, imagine a zona que ia ser. Cada linha ou cada palavra de uma pessoa ia ficar misturado com o que a outra pessoa tá querendo escrever por cima. Ou seja, seja lá o que eles gostariam de escrever no papel ia virar um monte de dados misturados. Na realidade você talvez gostaria que a primeira pessoa usasse o papel, e só quando terminasse a segunda pessoa usasse na sequência. Mas como eles estão disputando ao mesmo tempo, pode ser que o inverso aconteça, e como não sabemos quem vai ganhar a disputa de quem começa a usar o papel na frente do outro literalmente temos uma condição de corrida, ou o que se chama de race condition, que pode levar a resultados inesperados ou não-determinísticos.</p>
<p>Então como fazer nesses casos? Pra isso precisamos de um sinalizador. Quando a primeira pessoa quiser escrever, ele coloca uma placa dizendo “ocupado”. Quando a segunda pessoa for escrever também ele vê a placa e espera. Quando a primeira pessoa termina o que queria, ele tira a placa de “ocupado”. Agora a segunda pessoa pode colocar a placa dela e escrever sua parte, e depois tirar a placa. Se ela vai embora e esquece de tirar a placa, aquele pedaço de papel fica bloqueado e ninguém mais consegue escrever nele. Agora pense que a primeira pessoa bloqueia o pedaço de papel, então a segunda pessoa resolve bloquear outro pedaço de papel que a primeira vai precisar na sequência. Mas ambos esquecem de tirar as placas de ocupado de seus respectivos papéis e cada um quer usar o papel do outro. Temos um impasse, onde nenhum sinaliza pro outro que já terminou e ambos param de trabalhar esperando. Eles estão literalmente mortos por causa do bloqueio, ou seja, o que chamamos de dead-lock.</p>
<p>Essa placa de “ocupado” é o que em programação multi-thread chamamos de lock ou mutex, que significa mutuamente exclusivo. É um sinalizador de exclusividade. E como no exemplo, quem bloqueia o recurso também tem a responsabilidade de desbloquear quando acabar. E com isso você consegue ver como duas threads, mesmo se tiver dois cores na CPU onde eles teoricamente poderiam trabalhar independentes em paralelo, se tiver um recurso compartilhado como o tal pedaço de papel, um é obrigado a esperar o outro terminar.</p>
<p>Eu tentei usar metáforas aqui mas você só vai entender realmente o que tudo isso significa o dia que realmente começar a programar usando esses recursos. Entenda alguns fatos primeiro: toda CPU moderna é feita pra rodar threads, é como se fosse a menor unidade de execução. Cada thread executa uma sequência de instruções e tem um contexto, que é seu material de trabalho, que fica nos registradores da CPU. Quando o CPU faz o tal context switching, ele guarda o que está nos registradores naquele momento e deixa outra thread executar. Fazer essa troca de contexto não sai de graça, porque tem o trabalho de tirar o material da mesa e colocar o material do outro na mesa. Então quanto mais threads você tenta executar num core, mais devagar o trabalho geral vai ficar. Então não adianta ficar criando threads como um idiota achando que tudo vai rodar em paralelo e ficar mais rápido, lógico que não. O ideal é ter o mesmo número de threads que número de cores no seu CPU. Por outro lado não é porque você tem 2 cores e inicia 2 threads que ambos vão rodar da forma mais eficiente, porque eles podem estar compartilhando o mesmo recurso e esperando liberar o mutex que o outro colocou. Nesse caso uma terceira thread poderia se aproveitar desse período de espera e executar enquanto isso, caso não precise usar o mesmo recurso. Então você vê como também ter exatamente o mesmo número de threads que cores não é um regra. Em muitos casos determinar qual o número ideal de threads que você pode subir naquela determinada máquina é uma questão de tentativa e erro.</p>
<p>Criar processos é um procedimento que custa pro sistema operacional, não é de graça. Ele precisa carregar o binário do programa, alocar memória do sistema, checar permissões de segurança, etc. Esse procedimento inclusive é diferente entre sistemas operacionais. Em particular, sistemas Linux são mais rápidos pra criar processos do que um MacOS e uma ordem de grandeza mais rápido do que um Windows. Já falei em outro episódio que no Linux muitos programas como o Apache tomam vantagem de fazer o tal FORK que cria uma cópia rápida do processo atual pra gerar outro processo que vai rodar concorrentemente. E fazer forks em Linux é uma operação comparativamente mais barata do que no Windows. Ou seja, só porque ambos fazem a mesma coisa não quer dizer que fazem do mesmo jeito. Qualquer sistema que dependa de forks vai rodar ordens de grandeza pior num Windows.</p>
<p>Por isso mesmo desenvolvimento em Windows privilegia e recomenda criar um único processo por aplicação mas usar mais threads dentro do mesmo processo. Como uma thread compartilha a memória interna do processo com outras threads, a ação de criar threads é ordens de grandeza mais barato do que criar novos processos. Por outro lado isso também é uma troca. Processos são isolados de outros processos e um não vai pisar no calo do outro. Você não tem race conditions, você não precisa manualmente gerenciar mutexes e nem se preocupar com dead-locks, mas processos em Windows são mais caros pra criar. Por outro lado threads são bem mais baratas, só que agora temos o trabalho de nos preocuparmos em ter que manualmente codificar mutexes e tudo mais.</p>
<p>Programação é sempre o ato de se comprometer, você aceita uma vantagem em troca de uma desvantagem. Tudo sempre depende e nunca tem uma resposta certa pra tudo. Alguém poderia dizer que porque threads são mais rápidas de criar e usam menos recursos, eles são obviamente melhores do que fazer forks de processos. Por outro lado alguém pode argumentar que como temos que gerenciar mutexes e coisas assim manualmente, você tem muito mais bugs e situações não-determinísticas com threads e, portanto, processos são infinitamente mais fáceis de programar com menos bugs. Ambos estão certos.</p>
<p>Na prática você vai encontrar mais soluções baseadas em fork de processos em sistemas Linux ou UNIX como no MacOS e mais soluções multi-thread no Windows. Mas isso não é uma regra e em alguns casos, a melhor opção não necessariamente é a mais óbvia. Por exemplo, um navegador web como Firefox ou Chrome. Acho que se você é desenvolvedor já sabe que eles usam recursos de multi-processos. Antigamente um navegador só abrir uma única página de cada vez. Mas aí inventaram a idéia de abas e abrir múltiplos sites diferentes em múltiplas abas. Como um navegador é razoavelmente pesado e consome muita memória pra guardar tantas imagens e javascript que cada página carrega, você quer tentar usar a menor quantidade de memória quanto possível. Então o primeiro instinto de um programador é fazer cada aba ser uma thread.</p>
<p>De fato você vai usar menos recursos, mas à medida que os navegadores vão ficando mais e mais complexos, a quantidade de bugs por causa de threads aumentam exponencialmente. Agora um bug numa thread pode desestabilizar o navegador inteiro porque dentro do processo do navegador, uma thread tem acesso a tudo que as outras threads tem acesso. Então era muito comum você abrir um site qualquer e de repente seu navegador inteiro com 20 abas dava pau e você perdia tudo.</p>
<p>Pra piorar, alguns desses bugs eram bugs de segurança. Imagine uma aba que abre um site conseguir vasculhar a memória da outra aba que está com um e-commerce aberto e com seus dados de cartão de crédito na memória global. Cada vez que você tenta consertar um bug, pode acabar acidentalmente largando um bug novo no lugar. Então é como tentar tampar o sol com a peneira. Some a isso a possibilidade de instalar plugins que ganham acesso à memória do navegador como os plugins de Java ou os antigos de Flash e você tem um desastre esperando pra acontecer.</p>
<p>Por isso muitos navegadores começaram a separar certas partes em processos independentes como o Safari que iniciou separando coisas como Flash e Java em processos mas ainda manteve muita coisa em threads pra usar menos memória. Mas no caso do Google Chrome ele resolveu separar completamente cada aba em seu próprio processo. Por um lado isso trouxe muito mais estabilidade. E você já deve ter visto muitas vezes uma aba inteira crashear, mas o navegador continuar rodando sem nenhum problema. E você pode ver cada processo separado num Task Manager do Windows por exemplo, e inclusive pode matar um processo por lá mesmo, na mão. É a grande vantagem de se separar tudo em processos como eu já falei antes. Por outro lado todo mundo sabe como o Chrome hoje é um dos aplicativos que mais come RAM do seu sistema e essa separação é uma das razões já que código comum entre as diversas abas precisa ser copiada em outro processo a cada nova aba.</p>
<p>Conceitualmente falando, um fork de um processo é como se você pausasse esse processo e fizesse uma cópia de tudo que ele tem na memória pra outro espaço isolado e resumisse os dois, e a partir daí cada um tem vida independente. Se um processo está consumindo 1 MB de RAM, com um fork você deveria estar agora usando 2 MB. Mas no Linux temos um recurso chamado copy-on-write ou CoW, onde o segundo processo não usa praticamente nenhuma memória adicional e fica apontando pra memória do processo original assim que é criado. Se a memória não for modificada, ambos podem reaproveitar o mesmo dado em vez de fazer cópias desnecessárias e só coisas que o segundo processo começa a gravar começa a usar mais memória e fica invisível ao processo original e vice-versa. No Windows o equivalente ao fork realmente duplica a memória, que é outro motivo de porque criar processos no Windows é considerado mais caro.</p>
<p>Um fork com copy-on-write no Linux cada processo tem acesso a uma cópia da memória do processo pai, mas diferente de threads dentro do processo que compartilha tudo, não existe a necessidade de mutexes ou outro tipo de controle de acesso porque a partir do ponto do fork, cada processo tem sua cópia da memória pai e um não escreve em cima da memória do outro. Isso faz as coisas serem muito mais simples de programar. Claro, threads ainda são mais leves e usam muito menos memória no total.</p>
<p>Você pensa no Linux como um sistema estático que é sempre igual, mas ele mudou radicalmente dos anos 90 pros anos 2000 e até agora. A interface que você interage com o sistema, que seria os shells de linha de comando, ou as APIs, são parecidas, muito porque ele segue o que chamamos de padrão POSIX que é sigla de Portable Operating System Interface onde o X no final denota sua herança de UNIX. Teoricamente os UNIX e seus clones como Linux deveriam seguir esse padrão pros programas serem portáveis entre eles, no nível do código fonte, porque os binários não são compatíveis.</p>
<p>No mundo POSIX você tem a abstração de Posix threads ou pthreads implementado como LinuxThreads anos atrás. Threads em Linux eram bugados e nunca foram grande coisa até que finalmente alguns projetos começaram a surgir pra reimplementar o suporte de threads no Linux, como o Next Generation Posix Threads ou NGPT da IBM que eu me lembro que uma característica marcante era ser um mapeamento M pra N, ou seja uma thread em Linux não seria equivalente um pra um com uma thread de verdade.</p>
<p>Mas o projeto que ganhou foi o Native Posix Thread Library ou NPTL de Ulrich Drapper que era mantenedor do glibc e Ingo Molnár que vou citar de novo a seguir. O NPTL é uma implementação um pra um em vez de ser M pra N como o NGPT, portanto era bem mais simples de ser implementado. Esse projeto corrige as falhas do LinuxThreads e só a partir desse ponto dá pra se dizer que threads em Linux ficaram decentes. E isso só é um fato a partir da kernel 2.6 mais ou menos por volta de 2007. Pra ver como as coisas são diferentes entre diferentes Unix, o Solaris usava uma estratégia M pra N até o Solaris 9. O FreeBSD e NetBSD eram M pra N também mas acho que o FreeBSD também ia ou foi pra uma pra um. Parece que um pra um começou a ganhar terreno mas vamos voltar nisso em breve, guarde esse ponto.</p>
<p>Eu já expliquei sobre o tal scheduler na metáfora das mesas sendo o supervisor que força uma pessoa a sair da mesa pra dar lugar pra outra. Existem diferentes estratégias que um scheduler pode tomar. Quando você só tem uma mesa, você é obrigado a deixar uma pessoa trabalhando e mandar as outras esperarem a vez, e o supervisor manda trocar de lugar com o próximo pra ir dando chance das outras trabalharem também. Mas imagine se o supervisor resolve deixar uma pessoa que ainda não está pronta sentar na mesa e ela fica sem fazer nada, enquanto as outras estão esperando.</p>
<p>E se você adicionar uma segunda mesa, parece que fica mais fácil, mas na verdade fica mais difícil. Imagina se você tem três pessoas pra usar as duas mesas. Mesma coisa, você vai rotacionando uma pessoa em cada mesa. Mas imagina o trampo de uma pessoa ficar transportando seu trabalho de uma mesa pra outra se o supervisor não for muito legal e não deixar ficar na mesma mesa. Quando você tem duas mesas, não dá pra deixar seu trabalho no canto da mesa pra voltar pra ela rápido na próxima rodada necessariamente, você pode ter que ficar transportando suas coisas de um lado pro outro, e obviamente isso é mais lento.</p>
<p>E se você não tomar cuidado, e seu scheduler for meio burrinho, ele vai dando vez pra threads que não precisam, ou deixa threads com pouco trabalho ocupando tempo demais na sua vez, ou pior, cortando no meio o trabalho de quem realmente precisa rodar. E por isso no começo dos anos 2000 um Linux era ruim pra coisas como tocar um vídeo. O vídeo ficava engasgando, mesma coisa com áudio. Foi só em 2007 com a Kernel 2.6 que ganhamos o Completely Fair Scheduler ou CFS que implementa um algoritmo melhor, de autoria do Ingo Molnár que já mencionei antes, que se inspirou nas idéias de Con Kolivas. Em resumo esse CFS maximiza o uso da CPU e tenta dar preferência pra programas interativos, como aplicativos desktop, que diferente de processamento intensivo, tende a ficar ocioso por períodos de tempo e de repente precisar de prioridade pra coisas como tocar vídeo.</p>
<p>Cada sistema operacional implementa estratégias diferentes de scheduler, MacOS, Windows, BSDs, Linux e outros priorizam coisas diferentes e usam algoritmos diferentes. O próprio Windows demorou anos pra chegar no nível de granularidade do MacOS que sempre foi considerado melhor principalmente em criação multimídia porque ele não engasgava no gerenciamento de threads. Por isso músicos e DJs sempre preferiram Macs, especialmente em apresentações ao vivo: porque eles sabiam que o áudio não ia engasgar do nada porque o scheduler resolveu do nada dar a vez pra outra thread à toa. Isso acontecia com Windows e com Linux por isso eles eram bem inúteis em situações assim.</p>
<p>De novo, entenda que threads também tem comportamentos diferentes em diferentes sistemas operacionais. Quem saiu na frente na estratégia de threads foram os Macs no começo dos anos 2000 com o OS X. Windows era o segundo melhor, mais ou menos, e foi passado pelos Linux com o CFS só depois de 2007. Ou seja, o Linux moderno que conhecemos hoje só tomou forma de verdade a partir de 2007. Em termos de engasgos você vai notar que o Windows ainda é menos polido que MacOS e também desktop Linux. E esse fator do scheduler antigo foi mais um motivo de porque no mundo Linux processos continuaram sendo importantes.</p>
<p>De 2007, agora vamos voltar quase uma década, em 1999 surgiu um paper que começou a ajudar a mudar a forma como pensamos sobre concorrência no mundo de redes e web. Estamos falando do lendário paper sobre o problema dos C10K de Dan Kegel do antigo site cdrom.com discutindo quando os servidores de FTP da Simtel conseguiam servir até 10 mil conexões simultâneas sobre uma ethernet de 1 gigabit. Começamos a usar esse termo pra discutir como servir grandes quantidades de conexões simultaneamente. 10 mil parece muita coisa, mas um só servidor do Whatsapp hoje serve mais de 2 milhões de conexões simultâneas. O problema é que existem diversas estratégias pra fazer isso.</p>
<p>Eu já mencionei I/O mas agora vou definir um pouco melhor. I/O é qualquer entrada e saída do sistema, arquivos, rede, USB, teclado, monitor. Agora pense nos mainframes antigos que usavam dispositivos de fita, como os do IBM 701 que eu falei que gravavam dados a fantásticos 5.6 quilobytes por segundo. Se ele tivesse que ficar esperando a operação toda acabar o computador não ia fazer mais nada a não ser esperar. Por isso mainframes lendariamente tem milhares de canais paralelos de I/O. Mas o mesmo não acontece no barramento de computadores comuns como o que usamos.</p>
<p>Ou seja, mesmo que você consiga ter paralelismo disponível na sua CPU e sistema operacional, se você só tem um HD, não tem como milhares de threads escreverem ao mesmo tempo. O barramento de I/O vira gargalo. É como se você dividir o trabalho de escrever um livro entre 500 pessoas, onde cada uma escreve uma página. Então você vai acabar o livro no tempo que leva pra escrever uma única página. Porém, se todo mundo tiver que escrever no mesmo livro, não adiantou nada, só dá pra escrever uma página de cada vez.</p>
<p>Lembra quando falei de locks em memória global do processo compartilhado entre múltiplas threads? Algo semelhante acontece quando threads tentam acessar recursos restritos do sistema. É o que chamamos de operações que bloqueiam ou bloqueantes. Então se uma thread resolve escrever alguns gigabytes num arquivo, essa thread fica bloqueada até terminar, nesse caso outra thread pode executar outra coisa que não depende do HD enquanto a primeira fica bloqueada.</p>
<p>Conexões de rede também são bloqueantes, imagine uma thread fazendo download de alguma coisa ou algum cliente de rede pendurado numa thread. Essas operações são bloqueantes e a sequência de instruções da thread vai esperar até a operação terminar e desbloquear o processamento. Com o tempo os sistemas operacionais passaram a implementar algo chamado I/O assíncrono. Ou seja, em vez de bloquear o processamento, a thread pode pedir pra escrever alguma coisa num arquivo, mas o sistema operacional não vai bloquear a thread. Em vez disso a thread pede pra escrever e indica na função do sistema pra quando ele terminar, enviar um evento de término. Daí a thread pode configurar pra quando receber esse evento daí executar alguma outra sequência de instruções.</p>
<p>Ou seja, a programação fica dependente do evento de término da operação de I/O, seja operação de leitura ou de escrita, e é isso que muitos chamam de orientação a eventos. Isso não é nada novo, qualquer linguagem de programação dos anos 90 usada pra fazer aplicativos gráficos sempre usou programação orientada a eventos. Por exemplo, o código que é executado quando você tem o evento de clicar num botão, ou o evento de arrastar uma janela. Num Linux podemos enviar sinais a um processo, que é uma forma rudimentar de comunicação inter processos, como os sinais de kill como SIGTERM ou SIGINT. Aqui estamos estendendo para eventos de I/O do sistema operacional.</p>
<p>Mesmo sem I/O assíncrono podemos atender múltiplas conexões como no problema do C10K fazendo cada conexão ao servidor ser servida por forks do processo servidor. É como a primeira versão de um Apache resolvia isso. Cada nova requisição era gatilho pra um fork do processo principal que gerava um processo filho. Já expliquei que isso é pesado, especialmente em Windows.</p>
<p>A segunda alternativa é cada nova conexão recebida no processo servidor ser servida por uma nova thread dentro desse processo. Também já expliquei como threads são bem mais leves que um processo, mas temos o problema de gerenciar o compartilhamento da memória interna do processo entre múltiplas threads e os bugs que isso pode causar como race conditions e dead locks. Além disso quando estamos falando de 10 mil conexões ou mais, seria necessário mais de 10 mil threads, e eu já expliquei como context switching entre threads não é algo tão simples assim nem barato, você precisa de um scheduler muito bom e mesmo assim cada thread também consome recursos do sistema operacional. Versões subsequentes do Apache e outros servidores web como o IIS no Windows se valeram de threads pra servir milhares de conexões, mas não era o bastante.</p>
<p>A terceira opção como já imaginaram é usar esse tal recurso de I/O assíncrono, que permite que uma única thread ordene múltiplas operações de I/O ao sistema operacional sem que ele fique bloqueado. E o sistema operacional se vira pra realizar essas operações e vai notificando à thread a medida que cada operação vai terminando.</p>
<p>Cada sistema operacional implementa esse sistema de filas de eventos para I/O de formas diferentes. Sistemas derivados de BSD como o FreeBSD ou mesmo o MacOS tem uma syscall chamada kqueue. O Windows usa uma coisa chamada I/O Completion Port ou IOCP. E os Linux tinham syscalls chamada select, depois poll e finalmente a mais usada agora que é a epoll. Assim como gerenciamento de threads e schedulers, cada uma dessas implementações se comportam de formas diferentes. Estão vendo como os sistemas operacionais são mais diferentes do que só uma interface gráfica diferente?</p>
<p>Como resultado de toda a discussão no fim dos anos 90 e começo dos 2000 em cima do paper do C10K de Dan Kegel surgiu um projeto que nasceu com o objetivo de superar o Apache em conseguir servir dezenas de milhares de conexões simultâneas usando o mínimo possível de recursos do sistema, ou seja, sem usar a estratégia de uma conexão por processo ou thread e sim usando uma combinação de processos e chamadas de I/O assíncrono. É assim que nasce o projeto do servidor web mais rápido e escalável que temos até hoje, o NGINX de Igor Sysoev que começou a ser desenvolvido a partir de 2002 com lançamento da versão estável em 2004.</p>
<p>O NGINX tem um processo master que carrega outros processos que ele chama de “workers” e você vai ver que a palavra “worker” é um termo muito usado em arquiteturas com concorrência. Cada worker consegue gerenciar milhares de sockets de conexões de navegadores requisitando páginas web, por exemplo. Em vez de gerar um fork ou uma thread por conexão, ele registra a requisição via epoll num Linux e implementa um loop de eventos. Pense que o programa do worker é um loop que fica fazendo syscalls não bloqueantes de I/O e cada vez que o loop repete ele checa se veio o evento de término e aí executa o que precisa e devolve a página de volta pro requisitante. É uma forma de numa única thread conseguir servir milhares ou dezenas de milhares de conexões simultaneamente.</p>
<p>Não sei se fica claro e isso é importante. Se fosse como era antigamente, e eu fizesse um loop com chamadas de I/O, digamos que ler o arquivo HTML do HD levasse 1 segundo e enviar o HTML pela rede pro navegador levasse 1 segundo - é bem menos que isso na realidade, claro. E agora digamos que esse processo recebesse 10 requisições da mesma página. Levaria pelo menos 20 segundos pra servir todo mundo. Com I/O assíncrono poderia levar pouco mais do que os 2 segundos pra servir as 10 páginas.</p>
<p>Numa metáfora, do jeito antigo é como se no restaurante todo cliente pedisse direto pro chef o prato que quer, ele só consegue atender e fazer um prato de cada vez e cada cliente na fila precisaria esperar o prato do cliente anterior ficar pronto pra pedir o seu. Ou seja, o último cliente da fila precisa esperar todos os clientes anteriores ter o prato pronto antes de poder pedir o seu. Agora adicione um garçom, todos os clientes fazem o pedido pro garçom um atrás do outro e podem se sentar. Agora o último cliente da fila é atendido bem mais rápido. E o garçom vai passando os pedidos pro chef, que daí vai devolvendo os pratos à medida que forem ficando prontos, é como o evento de término que eu falei antes, mas os clientes já todos foram atendidos, fizeram os pedidos e podem ficar sentados fazendo outra coisa. É mais ou menos a diferença entre só ter I/O bloqueantes e ter I/O não-bloqueante. Não fica todo mundo bloqueado esperando em sequência.</p>
<p>Voltando ao NGINX, como cada worker é uma única thread, o master sobe na média um worker por core disponível na CPU então numa CPU quad-core subiríamos 4 workers. Cada worker com um loop de eventos que fica esperando os eventos de término de cada requisição de I/O que ele recebe, com capacidade de servir dezenas de milhares de conexões simultaneamente. E como ele não precisa criar uma thread por conexão, ele não perde tempo nem recurso fazendo context switching. Então um NGINX num bom servidor consegue servir até milhões de conexões simultaneamente, especialmente se for servir coisas simples como ler uma página HTML estática e só devolver sem processar lógica nenhuma. Se você vem de Javascript talvez já tenha ouvido falar desse pattern de arquitetura, isso se chama um Reactor, porque dentro do loop ele reage aos eventos de I/O que vão terminando.</p>
<p>Na mesma época, por volta de 2002 outro framework também implementou o mesmo pattern de Reactor com um event loop e usando I/O assíncrono, Node.js? Nope, ainda não. Esse foi o framework Twisted implementado em Python em 2002. Assim além de servir páginas HTML muito rápido, a cada loop do Reactor você podia executar processamento escrito em Python. Twisted introduziu ainda outro conceito: os Deferreds ou Futures. Mas vou explicar isso no próximo episódio.</p>
<p>Nesta primeira parte eu queria deixar bem explícito conceitos que acho que a maioria dos programadores tem problemas em entender logo no começo: concorrência e paralelismo e como os sistemas foram evoluindo pra resolver esse problema. Aliás, eu não expliquei isso antes mas vamos definir agora: concorrência e paralelismo. Simplificando, lembram dos computadores até os anos 90? Você podia ter tarefas que pausam e deixam outras executarem no seu lugar e ficam trocando de lugar, fazendo o tal context switching. Elas são concorrentes mas não são paralelas. Quando você tem dois ou mais cores na CPU aí sim, você realmente tem paralelismo, porque duas tarefas concorrentes, por exemplo threads, podem ser executadas verdadeiramente em paralelo. Seja em uma CPU com capacidade pra somente uma thread ou uma CPU com capacidade pra múltiplas threads, ainda assim você precisa de um scheduler, porque seu programa pode modelar e gerenciar mais threads concorrentes do que existem cores físicas pra executá-las.</p>
<p>É importante também que todos entendam como os sistemas operacionais são muito diferentes especialmente nesses aspectos de concorrência, como implementam threads de maneiras diferentes, como existem diferentes schedulers e no Linux você pode inclusive escolher schedulers diferentes pra casos diferentes. E como I/O assíncrono é outro recurso implementado de formas diferentes. Sabendo desses conceitos no próximo episódio vamos falar de coisas mais abstratas como corotinas, fibers e o conceito de deferreds ou futures.</p>
<p>Esse assunto é bem cabeludo, concorrência e paralelismo não tem uma resposta única que resolve todos os problemas. Cada tipo de problema vai precisar de uma solução diferente. Forks de processos funcionam. Multi-threads funcionam. I/O assíncrono é só mais uma opção nessa caixa de ferramentas e ainda vamos explorar outras opções. Além disso no mundo Linux o suporte a threads só ficou realmente bom a partir de 2007. Por isso você vê tanta coisa interessante no mundo de concorrência dando as caras principalmente na década de 2010. Muitos acham que event loops, o pattern de Reactor nasce com Node.js a partir de 2009, mas não, NGINX e Twisted pelo menos, já implementavam isso em 2002, derivados diretamente do objetivo de resolver o problema C10K de Dan Kegel de 1999 e esses conceitos mesmo já vinham pesquisas de anos antes.</p>
<p>Se tiverem dúvidas não deixem de mandar nos comentários abaixo, se curtiram o vídeo mandem um joinha, compartilhem com seus amigos, assinem o canal e não deixem de clicar no sininho pra não perderem os próximos episódios. A gente se vê semana que vem, até mais.</p>
<p>Links:</p>
<ul>
<li>The Linux Scheduler: a Decade of Wasted Cores (<a href="https://blog.acolyer.org/2016/04/26/the-linux-scheduler-a-decade-of-wasted-cores/"target="_blank" rel="noopener">https://blog.acolyer.org/2016/04/26/the-linux-scheduler-a-decade-of-wasted-cores/</a>)</li>
<li>The C10K problem (<a href="http://www.kegel.com/c10k.html"target="_blank" rel="noopener">http://www.kegel.com/c10k.html</a>)</li>
</ul>
]]></content:encoded><category>sistemas-distribuidos</category><category>backend</category><category>performance</category><category>akitando</category></item><item><title>Ex Manga Downloadr - Parte 6: A Ascensão do FLOW</title><link>https://akitaonrails.github.io/2017/06/13/ex-manga-downloadr-parte-6-a-ascensao-do-flow/</link><guid isPermaLink="true">https://akitaonrails.github.io/2017/06/13/ex-manga-downloadr-parte-6-a-ascensao-do-flow/</guid><pubDate>Tue, 13 Jun 2017 15:59:00 -0300</pubDate><description>&lt;p&gt;Faz bem mais de um ano desde que escrevi sobre meu projeto pessoal &lt;a href="https://github.com/akitaonrails/ex_manga_downloadr"target="_blank" rel="noopener"&gt;Ex Manga Downloadr&lt;/a&gt;. Desde então fiz apenas pequenas atualizações para acompanhar as versões mais recentes do Elixir e das bibliotecas.&lt;/p&gt;
&lt;p&gt;Para recapitular rapidamente: o exercício é fazer web scraping no MangaReader.net, baixar um monte de imagens organizadas em páginas e capítulos e, no final, compilar PDFs organizados para poder ler num Kindle.&lt;/p&gt;
&lt;p&gt;Web scraping é basicamente um loop de HTTP GETs em cima de toneladas de URLs, raspando o HTML e coletando mais URLs para baixar.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Faz bem mais de um ano desde que escrevi sobre meu projeto pessoal <a href="https://github.com/akitaonrails/ex_manga_downloadr"target="_blank" rel="noopener">Ex Manga Downloadr</a>. Desde então fiz apenas pequenas atualizações para acompanhar as versões mais recentes do Elixir e das bibliotecas.</p>
<p>Para recapitular rapidamente: o exercício é fazer web scraping no MangaReader.net, baixar um monte de imagens organizadas em páginas e capítulos e, no final, compilar PDFs organizados para poder ler num Kindle.</p>
<p>Web scraping é basicamente um loop de HTTP GETs em cima de toneladas de URLs, raspando o HTML e coletando mais URLs para baixar.</p>
<p>Em linguagens mais simples, as pessoas costumam resolver isso de dois jeitos ingênuos:</p>
<ul>
<li>Um loop aninhado simples. Uma única thread, fetches sequenciais. Se você tem 5.000 links e cada um leva 10 segundos para buscar, são 10 * 5.000 = 50.000 segundos — um tempo ridiculamente longo.</li>
<li>Spawn de processos, fibers, threads ou I/O paralelo em um reactor, tudo de uma vez. Uma tentativa de paralelizar todos os fetches simultaneamente.</li>
</ul>
<p>Todo mundo concorda que a primeira opção é ingênua. Mas a segunda tem seus problemas.</p>
<p>O ponto complicado é o <strong>CONTROLE</strong>.</p>
<p>Qualquer pessoa em Go diria <em>&ldquo;ah, é fácil, é só fazer um loop e disparar um monte de goroutines&rdquo;</em>, ou qualquer pessoa em Node.js diria <em>&ldquo;ah, é fácil, é só fazer um loop, chamar o fetch — tudo roda assincronamente — e adicionar callbacks, um async/await básico.&rdquo;</em></p>
<p>Não estão errados, mas ainda é uma implementação ingênua demais. Disparar centenas ou milhares de requisições paralelas é trivial. O problema vem depois: o que acontece se uma falha e você precisar fazer retry? E se o MangaReader tiver um sistema de throttling que começa a derrubar ou travar conexões? E se sua banda de internet simplesmente não der conta e, a partir de um certo ponto, você começar a ter retornos decrescentes e timeouts?</p>
<p>A mensagem é: disparar coisas em paralelo é trivialmente fácil. Controlar coisas em paralelo é complicado de verdade.</p>
<p>Por isso, na minha primeira implementação em Elixir, introduzi uma solução mais elaborada: uma combinação de um GenServer customizado, a infraestrutura de Task do próprio Elixir para o padrão async/await, e o <a href="https://github.com/devinus/poolboy"target="_blank" rel="noopener">Poolboy</a> para controlar a taxa de paralelismo. É assim que você controla o gargalo de recursos lentos: usando pools e filas (por isso todo banco de dados decente tem um connection pool — lembra do <a href="https://sourceforge.net/projects/c3p0/"target="_blank" rel="noopener">C3P0</a>?)</p>
<p>Um trecho da minha implementação mais antiga:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">chapter_page</span><span class="p">(</span><span class="o">[</span><span class="n">chapter_link</span><span class="p">,</span> <span class="n">source</span><span class="o">]</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="no">Task</span><span class="o">.</span><span class="n">Supervisor</span><span class="o">.</span><span class="n">async</span><span class="p">(</span><span class="no">Fetcher</span><span class="o">.</span><span class="n">TaskSupervisor</span><span class="p">,</span> <span class="n">fn</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="ss">:poolboy</span><span class="o">.</span><span class="n">transaction</span> <span class="ss">:worker_pool</span><span class="p">,</span> <span class="n">fn</span><span class="p">(</span><span class="n">server</span><span class="p">)</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="no">GenServer</span><span class="o">.</span><span class="n">call</span><span class="p">(</span><span class="n">server</span><span class="p">,</span> <span class="p">{</span><span class="ss">:chapter_page</span><span class="p">,</span> <span class="n">chapter_link</span><span class="p">,</span> <span class="n">source</span><span class="p">},</span> <span class="vi">@genserver_call_timeout</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span><span class="p">,</span> <span class="vi">@task_async_timeout</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Feio, né? E ainda tem todo o boilerplate do GenServer, do Supervisor customizado para inicializar o Poolboy e por aí vai. O código de workflow de alto nível ficava assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">pages</span><span class="p">({</span><span class="n">chapter_list</span><span class="p">,</span> <span class="n">source</span><span class="p">})</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">   <span class="n">pages_list</span> <span class="o">=</span> <span class="n">chapter_list</span>
</span></span><span class="line"><span class="cl">     <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="no">Worker</span><span class="o">.</span><span class="n">chapter_page</span><span class="p">(</span><span class="o">[&amp;</span><span class="mi">1</span><span class="p">,</span> <span class="n">source</span><span class="o">]</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">     <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="no">Task</span><span class="o">.</span><span class="n">await</span><span class="p">(</span><span class="o">&amp;</span><span class="mi">1</span><span class="p">,</span> <span class="vi">@await_timeout_ms</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">     <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">reduce</span><span class="p">(</span><span class="o">[]</span><span class="p">,</span> <span class="n">fn</span> <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">list</span><span class="p">},</span> <span class="n">acc</span> <span class="o">-&gt;</span> <span class="n">acc</span> <span class="o">++</span> <span class="n">list</span> <span class="k">end</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">   <span class="p">{</span><span class="n">pages_list</span><span class="p">,</span> <span class="n">source</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Dentro do módulo <code>Worker</code>, cada método público encapsulava as chamadas internas ao GenServer em um <code>Task async</code>, e na iteração da coleção adicionávamos <code>Task.await</code> para aguardar todas as chamadas paralelas terminarem — só então reduziamos os resultados.</p>
<p>O Elixir agora vem com uma infraestrutura muito interessante chamada <a href="https://elixir-lang.org/blog/2016/07/14/announcing-genstage/"target="_blank" rel="noopener"><code>GenStage</code></a>, que promete substituir o <code>GenEvent</code>. O caso de uso ideal é quando você tem uma situação de produtor-consumidor com back-pressure — exatamente quando há endpoints lentos e você precisa controlar gargalos.</p>
<p>Aí vem o <a href="https://github.com/elixir-lang/flow"target="_blank" rel="noopener">Flow</a>, uma abstração de alto nível que você usa de forma quase idêntica ao <code>Enum</code> nas suas coleções — mas em vez de mapeamento sequencial, ele cuida do traversal paralelo e do controle de lotes. O código fica muito parecido, só que sem você precisar gerenciar os controles de paralelização na mão.</p>
<p>Esse é o <a href="https://github.com/akitaonrails/ex_manga_downloadr/commit/b117f5236098f6d37e332633acb787be46a09d84"target="_blank" rel="noopener">commit completo</a> onde consegui remover o Poolboy, remover o meu GenServer customizado, reimplementar o Worker como um módulo simples de funções e fazer o workflow descartar o padrão async/await em favor do Flow:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">pages</span><span class="p">({</span><span class="n">chapter_list</span><span class="p">,</span> <span class="n">source</span><span class="p">})</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">   <span class="n">pages_list</span> <span class="o">=</span> <span class="n">chapter_list</span>
</span></span><span class="line"><span class="cl">     <span class="o">|&gt;</span> <span class="no">Flow</span><span class="o">.</span><span class="n">from_enumerable</span><span class="p">(</span><span class="ss">max_demand</span><span class="p">:</span> <span class="vi">@max_demand</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">     <span class="o">|&gt;</span> <span class="no">Flow</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="no">MangaWrapper</span><span class="o">.</span><span class="n">chapter_page</span><span class="p">(</span><span class="o">[&amp;</span><span class="mi">1</span><span class="p">,</span> <span class="n">source</span><span class="o">]</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">     <span class="o">|&gt;</span> <span class="no">Flow</span><span class="o">.</span><span class="n">partition</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">     <span class="o">|&gt;</span> <span class="no">Flow</span><span class="o">.</span><span class="n">reduce</span><span class="p">(</span><span class="n">fn</span> <span class="o">-&gt;</span> <span class="o">[]</span> <span class="k">end</span><span class="p">,</span> <span class="n">fn</span> <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">list</span><span class="p">},</span> <span class="n">acc</span> <span class="o">-&gt;</span> <span class="n">acc</span> <span class="o">++</span> <span class="n">list</span> <span class="k">end</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">     <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">to_list</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">   <span class="p">{</span><span class="n">pages_list</span><span class="p">,</span> <span class="n">source</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O único boilerplate que sobrou é o <code>Flow.from_enumerable()</code> e o <code>Flow.partition()</code> envolvendo o <code>Flow.map</code> — e pronto!</p>
<p>Repara que configurei <code>@max_demand</code> como 60. Você precisa ajustar esse valor para mais ou para menos dependendo da sua conexão com a internet — é questão de experimentar. Por padrão, o Flow dispara 500 processos em paralelo, o que é demais para web scraping numa conexão residencial normal: você vai sofrer retornos decrescentes. Era exatamente isso que eu controlava antes com o Poolboy, limitando o pool a cerca de 60 transações simultâneas no máximo.</p>
<p>Infelizmente, nem tudo é tão simples quanto parece. Rodando essa nova versão no modo de teste, o resultado foi:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>58,85s user 13,93s system 37% cpu 3:13,78 total</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Mais de 3 minutos no total, usando cerca de 37% da CPU disponível.</p>
<p>Minha versão anterior imediata, com toda aquela engenharia do Poolboy, Task.Supervisor, GenServer e companhia, ainda me dá isso:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>100,67s user 20,83s system 152% cpu 1:19,92 total</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Menos da <strong>METADE</strong> do tempo, ainda que usando todos os núcleos da CPU. Minha implementação customizada ainda aproveita melhor os recursos disponíveis. Tem algo na implementação com Flow que ainda não acertei. Já tentei aumentar o <code>max_demand</code> de 60 para 100 e não melhorou nada. Deixar no padrão 500 piora tudo — mais de 7 minutos.</p>
<p>De qualquer forma, pelo menos a implementação ficou muito mais fácil de ler (e consequentemente de manter). Mas ou a implementação com Flow tem gargalos que ainda não identifiquei, ou estou usando ele de forma errada. Se você souber o que é, deixa nos comentários.</p>
]]></content:encoded><category>elixir-e-erlang</category><category>performance</category><category>sistemas-distribuidos</category></item><item><title>Ex Pusher Lite - Parte 3 - Uma Solução Completa</title><link>https://akitaonrails.github.io/2016/12/30/ex-pusher-lite-parte-3-uma-solucao-completa/</link><guid isPermaLink="true">https://akitaonrails.github.io/2016/12/30/ex-pusher-lite-parte-3-uma-solucao-completa/</guid><pubDate>Fri, 30 Dec 2016 17:38:00 -0200</pubDate><description>&lt;p&gt;Já faz mais de um ano desde que escrevi as 2 partes sobre o meu conceito &amp;ldquo;Ex Pusher Lite&amp;rdquo;. O código de um ano atrás já está obsoleto, pois eu ainda estava aprendendo Elixir e Phoenix na prática.&lt;/p&gt;
&lt;p&gt;Este mês publiquei um artigo sobre &lt;a href="http://www.akitaonrails.com/2016/12/06/coherence-e-exadmin-devise-e-activeadmin-para-phoenix"target="_blank" rel="noopener"&gt;ExAdmin e Coherence&lt;/a&gt; e outro sobre &lt;a href="http://www.akitaonrails.com/2016/12/23/aplicativo-elixir-phoenix-deploy-balanceamento-carga-digitalocean"target="_blank" rel="noopener"&gt;Deploy de Elixir no DigitalOcean&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A ideia é bem simples: é uma homenagem ao &lt;a href="https://pusher.com/"target="_blank" rel="noopener"&gt;Pusher&lt;/a&gt;. Se você já usou o Pusher, isso é muito parecido (embora com bem menos funcionalidades, claro).&lt;/p&gt;</description><content:encoded><![CDATA[<p>Já faz mais de um ano desde que escrevi as 2 partes sobre o meu conceito &ldquo;Ex Pusher Lite&rdquo;. O código de um ano atrás já está obsoleto, pois eu ainda estava aprendendo Elixir e Phoenix na prática.</p>
<p>Este mês publiquei um artigo sobre <a href="http://www.akitaonrails.com/2016/12/06/coherence-e-exadmin-devise-e-activeadmin-para-phoenix"target="_blank" rel="noopener">ExAdmin e Coherence</a> e outro sobre <a href="http://www.akitaonrails.com/2016/12/23/aplicativo-elixir-phoenix-deploy-balanceamento-carga-digitalocean"target="_blank" rel="noopener">Deploy de Elixir no DigitalOcean</a>.</p>
<p>A ideia é bem simples: é uma homenagem ao <a href="https://pusher.com/"target="_blank" rel="noopener">Pusher</a>. Se você já usou o Pusher, isso é muito parecido (embora com bem menos funcionalidades, claro).</p>
<p>Montei uma solução completa inspirada no Pusher, usando o framework Phoenix, deployada no Digital Ocean — e você já pode testar agora mesmo, é só se cadastrar em <a href="http://expusherlite.cm42.io"target="_blank" rel="noopener">expusherlite.cm42.io</a>.</p>
<p>Depois do cadastro, você terá um token secreto (não compartilhe, claro) e estará vinculado a uma Organization. A partir daí pode criar Applications dentro dessa Organization. Cada Application terá um token único para identificá-la.</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/576/expusherlite_cm42_io_registrations.png" alt="dashboard"  loading="lazy" /></p>
<p>Suponha que você queira criar uma aplicação Rails com uma funcionalidade de Chat. Qualquer versão de Rails serve — não precisa ser 5.0 e nem precisa de ActionCable.</p>
<p>Primeiro, vamos configurar o <code>config/secrets.yml</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>development:
  secret_key_base: b9a1...e7aa
  pusher_host: &#34;expusherlite.cm42.io&#34;
  org_id: acme-inc
  app_key: 0221...f193
  secret_token: 4036...f193
...
production:
  secret_key_base: &lt;%= ENV[&#34;SECRET_KEY_BASE&#34;] %&gt;
  pusher_host: &lt;%= ENV[&#39;PUSHER_LITE_HOST&#39;] %&gt;
  org_id: &lt;%= ENV[&#39;PUSHER_LITE_ORGANIZATION&#39;] %&gt;
  app_key: &lt;%= ENV[&#34;PUSHER_LITE_APP_KEY&#34;] %&gt;
  secret_token: &lt;%= ENV[&#34;PUSHER_LITE_SECRET_TOKEN&#34;] %&gt;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Substitua <code>pusher_host</code>, <code>org_id</code>, <code>app_key</code> e <code>secret_token</code> pelos valores que você criou antes.</p>
<p>Agora quero adicionar um <code>PageController</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="nb">require</span> <span class="s2">&#34;net/http&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s2">&#34;uri&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">PageController</span> <span class="o">&lt;</span> <span class="no">ApplicationController</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">index</span>
</span></span><span class="line"><span class="cl">    <span class="n">uri</span> <span class="o">=</span> <span class="no">URI</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="s2">&#34;http://</span><span class="si">#{</span><span class="no">Rails</span><span class="o">.</span><span class="n">application</span><span class="o">.</span><span class="n">secrets</span><span class="o">.</span><span class="n">pusher_host</span><span class="si">}</span><span class="s2">/api/sessions&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="no">Net</span><span class="o">::</span><span class="no">HTTP</span><span class="o">.</span><span class="n">post_form</span><span class="p">(</span><span class="n">uri</span><span class="p">,</span> <span class="p">{</span><span class="s2">&#34;token&#34;</span> <span class="o">=&gt;</span> <span class="no">Rails</span><span class="o">.</span><span class="n">application</span><span class="o">.</span><span class="n">secrets</span><span class="o">.</span><span class="n">secret_token</span><span class="p">})</span>
</span></span><span class="line"><span class="cl">    <span class="vi">@guardian_token</span> <span class="o">=</span> <span class="no">JSON</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">response</span><span class="o">.</span><span class="n">body</span><span class="p">)</span><span class="o">[</span><span class="s2">&#34;jwt&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">    <span class="no">Rails</span><span class="o">.</span><span class="n">logger</span><span class="o">.</span><span class="n">info</span> <span class="vi">@guardian_token</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>(Se você estiver conectando ao meu servidor online, é obrigatório usar SSL — troque a URL acima por &ldquo;https&rdquo;)</p>
<p>O que esse trecho faz é enviar o token secreto pelo lado do servidor, para o meu serviço, e receber de volta um JSON Web Token (JWT). Aí você passa esse JWT para o front-end para habilitar a autenticação.</p>
<p>No front-end podemos ter essa <code>app/views/page/index.html.erb</code> bem simples:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>&lt;h1&gt;Ex Pusher Lite - Rails Integration Example&lt;/h1&gt;

&lt;script type=&#34;text/javascript&#34; charset=&#34;utf-8&#34;&gt;
  window.guardian_token = &#34;&lt;%= @guardian_token %&gt;&#34;;
  window.org_id = &#34;&lt;%= Rails.application.secrets.org_id %&gt;&#34;
  window.app_key = &#34;&lt;%= Rails.application.secrets.app_key %&gt;&#34;;
  window.pusher_host = &#34;&lt;%= Rails.application.secrets.pusher_host %&gt;&#34;;
&lt;/script&gt;

&lt;div id=&#34;chat&#34; class=&#34;fixedContainer&#34;&gt;
&lt;/div&gt;

&lt;input type=&#34;text&#34; name=&#34;name&#34; id=&#34;name&#34; value=&#34;&#34; placeholder=&#34;Name&#34;/&gt;
&lt;input type=&#34;text&#34; name=&#34;message&#34; id=&#34;message&#34; value=&#34;&#34; placeholder=&#34;Message&#34;/&gt;
&lt;input type=&#34;checkbox&#34; name=&#34;channel&#34; id=&#34;channel&#34; value=&#34;api&#34;/&gt;
&lt;label for=&#34;channel&#34;&gt;send through API&lt;/label&gt;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Bem simples. Podemos ajustar o CSS (<code>app/assets/stylesheets/application.css</code>) só para deixar mais apresentável:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="p">.</span><span class="nc">fixedContainer</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">height</span><span class="p">:</span> <span class="mi">250</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">width</span><span class="p">:</span> <span class="mi">100</span><span class="kt">%</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">padding</span><span class="p">:</span><span class="mi">3</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">border</span><span class="p">:</span> <span class="mi">1</span><span class="kt">px</span> <span class="kc">solid</span> <span class="kc">black</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">margin</span><span class="p">:</span> <span class="mi">5</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">overflow</span><span class="p">:</span> <span class="kc">auto</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nt">body</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">font-family</span><span class="p">:</span> <span class="n">Helvetica</span><span class="p">,</span> <span class="n">Arial</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Por último, precisamos carregar o Javascript principal do servidor ExPusherLite. Edite o layout em <code>app/views/layouts/application.html.erb</code> e adicione esta linha logo após a tag de fechamento <code>&lt;/body&gt;</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;http://&lt;%= Rails.application.secrets.pusher_host %&gt;/js/pusher.js&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora podemos usar esse Javascript no <code>app/assets/javascripts/application.js</code> para ligar tudo. Esta é a parte relevante:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="nx">$</span><span class="p">(</span><span class="nb">document</span><span class="p">).</span><span class="nx">ready</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kd">var</span> <span class="nx">PusherLite</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s2">&#34;pusher_lite&#34;</span><span class="p">).</span><span class="k">default</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kd">var</span> <span class="nx">pusher</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">PusherLite</span><span class="p">(</span><span class="nb">window</span><span class="p">.</span><span class="nx">app_key</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">host</span><span class="o">:</span> <span class="nb">window</span><span class="p">.</span><span class="nx">pusher_host</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">jwt</span><span class="o">:</span> <span class="nb">window</span><span class="p">.</span><span class="nx">guardian_token</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">uniqueUserId</span><span class="o">:</span> <span class="s2">&#34;robot&#34;</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ssl: true - if you&#39;re connecting to my online server
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kd">var</span> <span class="nx">publicChannel</span> <span class="o">=</span> <span class="nx">pusher</span><span class="p">.</span><span class="nx">subscribe</span><span class="p">(</span><span class="s2">&#34;general&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">publicChannel</span><span class="p">.</span><span class="nx">bind</span><span class="p">(</span><span class="s2">&#34;new_message&#34;</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">payload</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="nx">chat</span> <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="s2">&#34;#chat&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nx">chat</span><span class="p">.</span><span class="nx">append</span><span class="p">(</span><span class="s2">&#34;&lt;p&gt;&lt;strong&gt;&#34;</span> <span class="o">+</span> <span class="nx">payload</span><span class="p">.</span><span class="nx">name</span> <span class="o">+</span> <span class="s2">&#34;&lt;/strong&gt; &#34;</span> <span class="o">+</span> <span class="nx">payload</span><span class="p">.</span><span class="nx">message</span> <span class="o">+</span> <span class="s2">&#34;&lt;/p&gt;&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">chat</span><span class="p">.</span><span class="nx">scrollTop</span><span class="p">(</span><span class="nx">chat</span><span class="p">.</span><span class="nx">prop</span><span class="p">(</span><span class="s2">&#34;scrollHeight&#34;</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">pusher</span><span class="p">.</span><span class="nx">joinAll</span><span class="p">();</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Podemos continuar no mesmo arquivo com o Javascript que se conecta ao campo de mensagem, ouvindo o evento de tecla &ldquo;Enter&rdquo; para enviar as mensagens:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl">  <span class="kd">var</span> <span class="nx">message_element</span> <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="s2">&#34;#message&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="nx">message_element</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s1">&#39;keypress&#39;</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">keyCode</span> <span class="o">!=</span> <span class="mi">13</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="nx">name_element</span>    <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="s2">&#34;#name&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="nx">check_element</span>   <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="s2">&#34;#channel&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="nx">payload</span> <span class="o">=</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="nx">name_element</span><span class="p">.</span><span class="nx">val</span><span class="p">(),</span> <span class="nx">message</span><span class="o">:</span> <span class="nx">message_element</span><span class="p">.</span><span class="nx">val</span><span class="p">()</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="nx">check_element</span><span class="p">.</span><span class="nx">prop</span><span class="p">(</span><span class="s2">&#34;checked&#34;</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">sendPusher</span><span class="p">(</span><span class="nx">payload</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">sendAPI</span><span class="p">(</span><span class="nx">payload</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nx">message_element</span><span class="p">.</span><span class="nx">val</span><span class="p">(</span><span class="s1">&#39;&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nb">window</span><span class="p">.</span><span class="nx">publicChannel</span> <span class="o">=</span> <span class="nx">publicChannel</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E é assim que enviamos mensagens para o ExPusherLite, seja diretamente via WebSockets full-duplex:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">sendPusher</span><span class="p">(</span><span class="nx">payload</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">&#34;sending through socket&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="nb">window</span><span class="p">.</span><span class="nx">publicChannel</span><span class="p">.</span><span class="nx">trigger</span><span class="p">(</span><span class="s1">&#39;new_message&#39;</span><span class="p">,</span> <span class="nx">payload</span> <span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Ou fazendo POST para a API disponível:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>function sendAPI(payload) {
  console.log(&#34;sending through API&#34;)
  $.ajax({
    type : &#39;POST&#39;,
    crossDomain: true,
    url : makeURL(&#34;new_message&#34;),
    headers : { Authorization : &#39;Bearer &#39; &#43; window.guardian_token },
    data : payload,
    success : function(response) {
      console.log(response);
      console.log(&#34;sent through API successfully&#34;);
    },
    error : function(xhr, status, error) {
      console.log(error);
    }
  });
}

function makeURL(event) {
  return &#34;http://&#34; &#43; window.pusher_host &#43; &#34;/api/organizations/&#34; &#43; window.org_id &#43; &#34;/applications/&#34; &#43; window.app_key &#43; &#34;/event/&#34; &#43; event;
}</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Vale dizer que você pode enviar mensagens pela API a partir do lado do servidor também. Especialmente de um processo ActiveJob para manter a aplicação web Rails rápida — e aproveitando para salvar a mensagem no banco de dados ou aplicar filtros.</p>
<p>E é isso! Sua aplicação Rails agora tem WebSockets. Dá pra ter o melhor dos dois mundos.</p>
<p>Se quiser ver o exemplo funcionando, publiquei um app de demo <a href="http://ex-pusher-lite-demo.herokuapp.com/"target="_blank" rel="noopener">no Heroku</a>. É só uma demo, sem autenticação, sem sanitização contra cross-scripting, sem nada.</p>
<p><a href="http://ex-pusher-lite-demo.herokuapp.com/"target="_blank" rel="noopener"><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/577/Screen_Shot_2016-12-30_at_17.42.22.png" alt="chat demo"  loading="lazy" /></a></p>
<p>Resumindo: é uma aplicação Rails (poderia ser Django, Laravel, ASP.NET MVC, tanto faz) se comunicando via WebSocket + APIs com um cluster Phoenix.</p>
<h3>Próximos Passos<span class="hx:absolute hx:-mt-20" id="próximos-passos"></span>
    <a href="#pr%c3%b3ximos-passos" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Continue acompanhando meu blog (ou meu Twitter em <a href="https://twitter.com/akitaonrails"target="_blank" rel="noopener">@akitaonrails</a>) para os próximos posts.</p>
<p>Ainda estou avaliando se vou abrir o código do ExPusherLite como open source — me avise se tiver interesse.</p>
<p>Também estou pensando em manter os servidores online como um serviço barato. Você pode usar de graça agora para brincar, mas não use em produção ainda. Como estou codificando pesado, vou ficar atualizando os servidores frequentemente, então não há SLA. Me avise se tiver interesse em um serviço assim que mantenha o código aberto para você poder confiar nele.</p>
<p>Ainda faltam recursos importantes, como suporte adequado a SSL, canais criptografados, APIs de Presence melhores e por aí vai — mas o que está disponível agora já cobre a maioria dos casos de uso para WebSockets.</p>
<p>E tem mais: por ser Phoenix, por ser Elixir, por ser Erlang, ganhamos PubSub distribuído &ldquo;de graça&rdquo;. Como expliquei no meu post de <a href="http://www.akitaonrails.com/2016/12/23/aplicativo-elixir-phoenix-deploy-balanceamento-carga-digitalocean"target="_blank" rel="noopener">deploy</a>, é um setup com um servidor em Nova York e outro em Londres, só para demonstrar a natureza distribuída do Erlang.</p>
<p>Foi muito divertido brincar com Elixir nos últimos dias e ver com que rapidez consegui montar uma solução completa como essa. Teve muitos quebra-cabeças que me fizeram coçar a cabeça: como lidar com problemas de cross origin, como fazer os nós se encontrarem pelo deploy via edeliver, descobrir as peças faltantes na transição do exrm para o distillery (que ainda está em andamento na comunidade), etc.</p>
<p>Agora estou bem confortável com o básico, do bootstrap do projeto até o deploy em cenário de cluster. E espero que esse serviço seja útil para mais pessoas.</p>
<p>Como este é possivelmente meu último post do ano: Feliz Ano Novo! E nos vemos em 2017!</p>
]]></content:encoded><category>elixir-e-erlang</category><category>sistemas-distribuidos</category><category>backend</category></item><item><title>Aplicativo Elixir Phoenix em Deploy num Setup com Balanceamento de Carga no DigitalOcean</title><link>https://akitaonrails.github.io/2016/12/23/aplicativo-elixir-phoenix-deploy-balanceamento-carga-digitalocean/</link><guid isPermaLink="true">https://akitaonrails.github.io/2016/12/23/aplicativo-elixir-phoenix-deploy-balanceamento-carga-digitalocean/</guid><pubDate>Fri, 23 Dec 2016 15:07:00 -0200</pubDate><description>&lt;p&gt;Uma das principais vantagens de construir uma aplicação web com Websockets usando Phoenix é como é &amp;ldquo;fácil&amp;rdquo; para o Erlang se conectar num cluster.&lt;/p&gt;
&lt;p&gt;Para começar, Erlang não precisa de múltiplos processos como Ruby (que fica limitado a uma conexão por processo, ou por thread se você usa um servidor com threads como o Puma). Um único processo Erlang vai tomar conta de toda a máquina, se necessário. Internamente ele mantém uma thread real por core da máquina. E cada thread tem seu próprio Scheduler para gerenciar quantos micro-processos você precisar. Você pode ler tudo sobre isso no meu post &lt;a href="http://www.akitaonrails.com/2015/11/25/yocto-services-e-meu-primeiro-mes-com-elixir"target="_blank" rel="noopener"&gt;&amp;ldquo;Yocto Services&amp;rdquo;&lt;/a&gt;.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Uma das principais vantagens de construir uma aplicação web com Websockets usando Phoenix é como é &ldquo;fácil&rdquo; para o Erlang se conectar num cluster.</p>
<p>Para começar, Erlang não precisa de múltiplos processos como Ruby (que fica limitado a uma conexão por processo, ou por thread se você usa um servidor com threads como o Puma). Um único processo Erlang vai tomar conta de toda a máquina, se necessário. Internamente ele mantém uma thread real por core da máquina. E cada thread tem seu próprio Scheduler para gerenciar quantos micro-processos você precisar. Você pode ler tudo sobre isso no meu post <a href="http://www.akitaonrails.com/2015/11/25/yocto-services-e-meu-primeiro-mes-com-elixir"target="_blank" rel="noopener">&ldquo;Yocto Services&rdquo;</a>.</p>
<p>Além disso, Erlang tem capacidades nativas de formar um cluster, onde cada instância Erlang age como um Node peer-to-peer, sem precisar de um coordenador centralizado. Você pode ler tudo sobre isso no meu post sobre <a href="http://www.akitaonrails.com/2015/11/25/exercicio-exmessenger-entendendo-nodes-em-elixir"target="_blank" rel="noopener">Nodes</a>. O poder do Erlang está em como é &ldquo;fácil&rdquo; formar sistemas distribuídos confiáveis.</p>
<p>Você pode subir várias instâncias Phoenix e, a partir de uma delas, transmitir mensagens para usuários inscritos em Channels mesmo que seus sockets estejam conectados a instâncias diferentes. É transparente e você não precisa fazer nada especial no seu código. Phoenix, Elixir e Erlang fazem todo o trabalho pesado por você nos bastidores.</p>
<h3>Sem Heroku para você :-(<span class="hx:absolute hx:-mt-20" id="sem-heroku-para-você--"></span>
    <a href="#sem-heroku-para-voc%c3%aa--" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Como você quer aproveitar esse recurso de escalabilidade e alta disponibilidade para sistemas distribuídos (no pequeno exemplo de um sistema de chat em tempo real), vai precisar ter mais controle sobre a infraestrutura. Esse requisito descarta a maioria das ofertas de Platform as a Service (PaaS), como o Heroku. O modelo do Heroku gira em torno de processos únicos e voláteis em containers isolados. Esses processos enjaulados (dynos) não têm consciência de outros processos ou da rede interna, então você não consegue subir Dynos e fazer com que formem um cluster porque eles não vão se encontrar.</p>
<p>Se você já sabe configurar coisas no Linux: Postgresql, HAproxy, etc., vá direto para a <a href="#phoenix-setup">seção específica do Phoenix</a>.</p>
<h3>IaaS (DigitalOcean) ao resgate!<span class="hx:absolute hx:-mt-20" id="iaas-digitalocean-ao-resgate"></span>
    <a href="#iaas-digitalocean-ao-resgate" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Você precisa de processos de longa duração em servidores alcançáveis pela rede (seja por rede privada, VPN, ou simplesmente - inseguro! - redes públicas).</p>
<p>Neste exemplo quero mostrar um deploy bem simples usando DigitalOcean (você pode escolher qualquer IaaS, como AWS, Google Cloud, Azure ou o que você se sentir mais confortável).</p>
<p>Criei 4 droplets (todos usando o menor tamanho de 512Mb de RAM):</p>
<ul>
<li>1 banco de dados Postgresql (ponto único de falha: não é o foco deste artigo construir um setup de banco de dados altamente disponível e replicado);</li>
<li>1 servidor HAProxy (ponto único de falha: novamente, não é o foco criar um esquema de balanceamento de carga altamente disponível);</li>
<li>2 servidores Phoenix - um no datacenter de NYC e outro no de Londres, para demonstrar como é fácil para o Erlang formar clusters mesmo com máquinas geograficamente separadas.</li>
</ul>
<h3>Configuração básica do Ubuntu 16.04<span class="hx:absolute hx:-mt-20" id="configuração-básica-do-ubuntu-1604"></span>
    <a href="#configura%c3%a7%c3%a3o-b%c3%a1sica-do-ubuntu-1604" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><ul>
<li>Objetivos: configurar o locale, garantir que as atualizações automáticas estejam ativas, atualizar pacotes, instalar e configurar Elixir e Node.</li>
<li>Você também deveria: mudar o SSH para outra porta e instalar [fail2ban](<a href="https://www.digitalocean.com/community/tutorials/how-to-protect-ssh-with-fail2ban-on-ubuntu-14-04"target="_blank" rel="noopener">https://www.digitalocean.com/community/tutorials/how-to-protect-ssh-with-fail2ban-on-ubuntu-14-04</a>, desabilitar login por senha.</li>
</ul>
<p>Recomendo ler meu post sobre <a href="http://www.akitaonrails.com/2016/09/21/ubuntu-16-04-lts-xenial-no-vagrant-com-vmware-fusion"target="_blank" rel="noopener">configurar o Ubuntu 16.04</a>. Resumindo, comece configurando o UTF-8 corretamente:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo locale-gen &#34;en_US.UTF-8&#34;
sudo dpkg-reconfigure locales
sudo update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8
echo &#39;LC_ALL=en_US.UTF-8&#39; | sudo tee -a /etc/environment
echo &#39;LANG=en_US.UTF-8&#39; | sudo tee -a /etc/environment</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Adicione um usuário adequado ao grupo sudo e, a partir de agora, não use mais o usuário root. Vou criar um usuário chamado <code>pusher</code> e vou explicar em outro post o porquê. Você deve criar um nome de usuário que faça sentido para a sua aplicação.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>adduser pusher
usermod -aG sudo pusher</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora saia e entre novamente com esse usuário. <code>ssh pusher@server-ip-address</code>. Se você estiver num Mac, copie a chave pública do seu certificado SSH assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>ssh-copy-id -i ~/.ssh/id_ed25519.pub pusher@server-ip-address</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Isso cria o <code>.ssh/authorized_keys</code> se não existir, define as permissões corretas e adiciona sua chave pública. Você pode fazer isso manualmente também, claro.</p>
<p>Os droplets do DigitalOcean não vêm com swap file e eu recomendaria adicionar um, especialmente se você quiser começar com as máquinas menores com menos de 1GB de RAM:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
sudo cp /etc/fstab /etc/fstab.bak
echo &#39;/swapfile none swap sw 0 0&#39; | sudo tee -a /etc/fstab
sudo sysctl vm.swappiness=10
sudo sysctl vm.vfs_cache_pressure=50
echo &#39;vm.swappiness=10&#39; | sudo tee -a /etc/sysctl.conf
echo &#39;vm.vfs_cache_pressure=50&#39; | sudo tee -a /etc/sysctl.conf</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Certifique-se de que as atualizações automáticas estejam configuradas. No mínimo você vai querer que as atualizações de segurança sejam instaladas automaticamente quando disponíveis.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo apt install unattended-upgrades</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora, vamos instalar Elixir e Node (Phoenix precisa de Node.js):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>wget https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb &amp;&amp; sudo dpkg -i erlang-solutions_1.0_all.deb
curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
sudo apt-get update
sudo apt-get upgrade
sudo apt-get install build-essential nodejs esl-erlang elixir erlang-eunit erlang-base-hipe
sudo npm install -g brunch
mix local.hex
mix archive.install https://github.com/phoenixframework/archives/raw/master/phoenix_new.ez # opcional, instale se quiser testar phoenix manualmente na sua máquina</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora você tem uma máquina pronta para Elixir. Crie um snapshot de imagem no DigitalOcean, mova para as regiões onde quer criar seus outros droplets e use essa imagem para criar quantos droplets precisar.</p>
<p>Para este exemplo, criei um segundo droplet na região de Londres, um terceiro para postgresql na região NYC1 e um quarto na região NYC3 para o HAProxy.</p>
<p>Vou me referir aos endereços IP públicos deles como <strong>&ldquo;nyc-ip-address&rdquo;</strong>, <strong>&ldquo;lon-ip-address&rdquo;</strong>, <strong>&ldquo;pg-ip-address&rdquo;</strong>, e <strong>&ldquo;ha-ip-address&rdquo;</strong>.</p>
<h3>Configuração básica do PostgreSQL<span class="hx:absolute hx:-mt-20" id="configuração-básica-do-postgresql"></span>
    <a href="#configura%c3%a7%c3%a3o-b%c3%a1sica-do-postgresql" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><ul>
<li>Objetivo: configuração básica do Postgresql para permitir que os servidores Phoenix se conectem.</li>
<li>A fazer: criar uma role secundária só para conectar ao banco da aplicação e outra superusuário para criar o banco e migrar o schema. Também trancar a máquina e configurar SSH tunnels ou outro método seguro, pelo menos uma rede privada, em vez de permitir conexões TCP na porta 5432 abertas para a Internet.</li>
</ul>
<p>Agora conecte em <code>ssh pusher@pg-ip-address</code> e siga isso:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo apt-get install postgresql postgresql-contrib</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Crie uma nova role com o mesmo nome do usuário que você adicionou acima (&ldquo;pusher&rdquo; no nosso exemplo):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>$ sudo -u postgres createuser --interactive

Enter name of role to add: pusher
Shall the new role be a superuser? (y/n) y

$ sudo -u postgres createdb pusher</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O Postgresql espera encontrar um banco de dados com o mesmo nome da role e a role deve ter o mesmo nome do usuário Linux. Agora você pode usar <code>psql</code> para definir uma senha para essa nova role:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>$ sudo -u postgres psql
\password pusher</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Registre uma senha segura, anote-a e vamos em frente.</p>
<p>O Postgresql vem bloqueado para conexões externas. Uma forma de conectar de fora é configurar seus servidores para criar um <a href="https://www.postgresql.org/docs/9.1/static/ssh-tunnels.html"target="_blank" rel="noopener">SSH tunnel</a> ao servidor de banco de dados e manter as conexões TCP externas pela porta 5432 proibidas.</p>
<p>Mas neste exemplo, vamos simplesmente permitir conexões da Internet pública na porta TCP 5432. <strong>Atenção:</strong> isso é MUITO inseguro!</p>
<p>Edite o <code>/etc/postgresql/9.5/main/postgresql.conf</code> e encontre a linha de configuração <code>listen_addresses</code> e permita:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>listen_addresses = &#39;*&#39;    # what IP address(es) to listen on;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Isso deve vincular o servidor à porta TCP. Agora edite <code>/etc/postgresql/9.5/main/pg_hba.conf</code> e edite o final para ficar assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># IPv4 local connections:
host    all             all             127.0.0.1/32            trust
host    all             all             your-local-machine-ip-address/24        trust
host    all             all             nyc-ip-address/24       trust
host    all             all             lon-ip-address/24       trust
# IPv6 local connections:
host    all             all             ::1/128                 trust</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Salve o arquivo de configuração e reinicie o servidor:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo service postgresql restart</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Viu o que fiz aí? Só permiti conexões vindas dos IPs públicos dos servidores Phoenix. Isso não torna o servidor seguro, só um pouco menos vulnerável. Se você está atrás de uma rede DHCP/NAT, só pesquise &ldquo;what&rsquo;s my IP&rdquo; no Google para ver seu IP público - que provavelmente é compartilhado por muitos outros usuários; lembre-se que você está permitindo conexões de um IP inseguro ao seu servidor de banco de dados! Depois de fazer os testes iniciais e criar seu schema, remova a linha <code>your-local-machine-ip-address/24</code> da configuração.</p>
<p>Na sua aplicação Phoenix, edite o arquivo local <code>config/prod.secret.exs</code> para ficar assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># Configure your database</span>
</span></span><span class="line"><span class="cl"><span class="n">config</span> <span class="ss">:your_app_name</span><span class="p">,</span> <span class="no">ExPusherLite</span><span class="o">.</span><span class="n">Repo</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="ss">adapter</span><span class="p">:</span> <span class="no">Ecto</span><span class="o">.</span><span class="n">Adapters</span><span class="o">.</span><span class="n">Postgres</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="ss">username</span><span class="p">:</span> <span class="s2">&#34;pusher&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="ss">password</span><span class="p">:</span> <span class="s2">&#34;your-super-secure-pg-password&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="ss">database</span><span class="p">:</span> <span class="s2">&#34;your-app-database-name&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="ss">hostname</span><span class="p">:</span> <span class="s2">&#34;pg-ip-address&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="ss">pool_size</span><span class="p">:</span> <span class="mi">20</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Substitua as informações pelo seu servidor e banco de dados, e agora você pode testar assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>MIX_ENV=prod iex -S mix phoenix.server</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Se você ver uma mensagem <code>:econnrefused</code> do postgrex, tem algo errado. Revise sua configuração, reinicie o servidor e tente novamente. Se tudo conectar, rode <code>MIX_ENV=prod mix do ecto.create, ecto.migrate</code> para preparar seu banco de dados.</p>
<p>Por fim, você vai querer fechar o restante do seu servidor com UFW, pelo menos. UFW já vem instalado no Ubuntu 16, então basta fazer:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>ufw allow 5432
ufw allow ssh
ufw enable</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Pronto. E de novo, isso não torna seu servidor seguro, só o torna menos inseguro. Há uma diferença enorme!</p>
<p>E por falar nisso, se você é fã de Docker:</p>
<blockquote>
  <p>NÃO INSTALE UM BANCO DE DADOS DENTRO DE UM CONTAINER DOCKER!</p>

</blockquote>
<p><a href="http://patrobinson.github.io/2016/11/07/thou-shalt-not-run-a-database-inside-a-container/"target="_blank" rel="noopener">Você foi avisado</a>!</p>
<h3>Configuração básica do HAProxy<span class="hx:absolute hx:-mt-20" id="configuração-básica-do-haproxy"></span>
    <a href="#configura%c3%a7%c3%a3o-b%c3%a1sica-do-haproxy" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><ul>
<li>Objetivos: fornecer uma solução simples de balanceamento de carga entre nossos 2 servidores Phoenix.</li>
<li>A fazer: há algo errado com a verificação de sessão ou algo assim, pois às vezes preciso atualizar o browser para não ser mandado de volta ao formulário de login na minha aplicação. Phoenix usa sessões baseadas em cookie, então não acho que esteja perdendo sessões.</li>
</ul>
<p>Agora vamos <code>ssh pusher@ha-ip-address</code>. Essa parte é fácil, basta instalar o HAProxy:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo apt-get install haproxy</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Edite <code>/etc/haproxy/haproxy.cfg</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>...
listen your-app-name
  bind 0.0.0.0:80
  mode http
  stats enable
  stats uri /haproxy?stats
  stats realm Strictly\ Private
  stats auth admin:some-secure-password-for-admin
  option forwardfor
  option http-server-close
  option httpclose
  balance roundrobin
  cookie SERVERID insert indirect nocache
  server us your_us_server_IP:8080 check cookie us1
  server uk your_uk_server_IP:8080 check cookie uk1</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Você pode omitir as linhas de <code>stats</code> se tiver outros meios de monitoramento, caso contrário defina uma senha segura para o usuário <code>admin</code>. Uma parte muito importante é o <code>option http-server-close</code>, conforme explicado <a href="http://blog.silverbucket.net/post/31927044856/3-ways-to-configure-haproxy-for-websockets"target="_blank" rel="noopener">neste outro post</a>, caso contrário você pode ter problemas com Websockets.</p>
<p>Por algum motivo estou tendo problemas com minha aplicação depois de fazer login e ela definir a sessão - às vezes preciso atualizar para ser redirecionado à página correta, ainda não sei o porquê e acredito que seja algo na configuração do HAProxy. Se alguém souber o que é, me avise nos comentários. Por ora, estou recorrendo a Sticky sessions (afinidade de servidor) fazendo o HAProxy escrever um cookie de volta com o servidor.</p>
<p>Agora reinicie o servidor e habilite o UFW também:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo service haproxy restart
sudo ufw allow http
sudo ufw allow https
sudo ufw allow ssh
sudo ufw enable</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Você pode facilmente adicionar suporte a SSL na sua aplicação configurando o HAProxy (e não os nodes Phoenix). A <a href="https://www.digitalocean.com/community/tutorials/how-to-secure-haproxy-with-let-s-encrypt-on-ubuntu-14-04"target="_blank" rel="noopener">documentação do DigitalOcean</a> é abrangente, é só seguir. No fim, meu <code>haproxy.cfg</code> ficou assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>global
   log /dev/log    local0
   log /dev/log    local1 notice
   chroot /var/lib/haproxy
   stats socket /run/haproxy/admin.sock mode 660 level admin
   stats timeout 30s
   user haproxy
   group haproxy
   daemon
   maxconn 2048
   tune.ssl.default-dh-param 2048

defaults
   log global
   mode http
   option httplog
   option dontlognull
   option redispatch
   option forwardfor
   option http-server-close
   timeout connect 5000
   timeout client  50000
   timeout server  50000

frontend www-http
   bind your_ha_proxy_IP:80
   reqadd X-Forwarded-Proto:\ http
   default_backend www-backend

frontend www-https
   bind your_ha_proxy_IP:443 ssl crt /etc/haproxy/certs/your_domain.pem
   reqadd X-Forwarded-Proto:\ https
   acl letsencrypt-acl path_beg /.well-known/acme-challenge/
   use_backend letsencrypt-backend if letsencrypt-acl
   default_backend www-backend

backend www-backend
   redirect scheme https if !{ ssl_fc }
   # setting session stickiness
   cookie SERVERID insert indirect nocache
   server us your_us_server_IP:8080 check cookie us1
   server uk your_uk_server_IP:8080 check cookie uk1

backend letsencrypt-backend
   server letsencrypt 127.0.0.1:54321</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Por fim, vou assumir que você tem um servidor/serviço de DNS em algum lugar onde pode registrar o IP deste servidor HAProxy como um registro A para acessá-lo por um nome completo como &ldquo;your-app-name.mydomain.com&rdquo;.</p>
<p><a name="phoenix-setup"></a></p>
<h3>Configuração básica do Phoenix<span class="hx:absolute hx:-mt-20" id="configuração-básica-do-phoenix"></span>
    <a href="#configura%c3%a7%c3%a3o-b%c3%a1sica-do-phoenix" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><ul>
<li>Objetivo: configurar a app Phoenix para ser deployável. Configurar os servidores com os arquivos de configuração necessários.</li>
<li>A fazer: descobrir uma forma de reduzir os tempos absurdamente longos de deploy.</li>
</ul>
<p>Finalmente, temos quase tudo no lugar.</p>
<p>Vou assumir que você já tem uma aplicação Phoenix funcionando, caso contrário crie uma a partir de qualquer um dos muitos tutoriais disponíveis.</p>
<p>Montei estas informações a partir de <a href="https://medium.com/@diamondgfx/deploying-phoenix-applications-with-exrm-97a3867ebd04#.7qyuplncx"target="_blank" rel="noopener">posts</a> como <a href="http://engineering.pivotal.io/post/how-to-set-up-an-elixir-cluster-on-amazon-ec2/"target="_blank" rel="noopener">este muito útil</a> do Pivotal sobre deploy baseado em AWS. Em resumo, você precisa fazer uma série de alterações na sua configuração.</p>
<p>Quando você está desenvolvendo a aplicação, vai notar que toda vez que roda, ela compila incrementalmente o que mudou. Os binários ficam em <code>_build/dev</code> ou <code>_build/test</code> na forma de binários <code>.beam</code> (similar ao que <code>.class</code> são para Java).</p>
<p>Diferente de Ruby, Python ou PHP, você não está fazendo deploy de código-fonte para servidores de produção. É mais parecido com Java, onde você precisa ter tudo compilado em binários e empacotado no que chamamos de <strong>release</strong>. É como um &ldquo;.war&rdquo; ou &ldquo;.ear&rdquo; se você vem do mundo Java.</p>
<p>Para criar esse pacote as pessoas costumavam usar &ldquo;exrm&rdquo;, mas está sendo substituído por <a href="https://github.com/bitwalker/distillery"target="_blank" rel="noopener">&ldquo;distillery&rdquo;</a>, então vamos usá-lo.</p>
<p>Aí, se você vem do Ruby está familiarizado com Capistrano. Ou se vem do Python, conhece o clone do Capistrano, Fabric. Elixir tem uma ferramenta similar (bem mais simples por enquanto), chamada <a href="https://github.com/boldpoker/edeliver"target="_blank" rel="noopener">&ldquo;edeliver&rdquo;</a>. É basicamente uma ferramenta de automação SSH.</p>
<p>Você adiciona eles ao <code>mix.exs</code> como qualquer outra dependência:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">application</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="o">[</span><span class="ss">mod</span><span class="p">:</span> <span class="p">{</span><span class="no">ExPusherLite</span><span class="p">,</span> <span class="o">[]</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">   <span class="ss">applications</span><span class="p">:</span> <span class="o">[...</span><span class="p">,</span> <span class="ss">:edeliver</span><span class="o">]]</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">defp</span> <span class="n">deps</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="o">[...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="p">{</span><span class="ss">:edeliver</span><span class="p">,</span> <span class="s2">&#34;~&gt; 1.4.0&#34;</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">   <span class="p">{</span><span class="ss">:distillery</span><span class="p">,</span> <span class="s2">&#34;~&gt; 1.0&#34;</span><span class="p">}</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="o">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Do post do Pivotal, o detalhe importante para não esquecer é editar esta parte no arquivo <code>config/prod.exs</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="ss">http</span><span class="p">:</span> <span class="o">[</span><span class="ss">port</span><span class="p">:</span> <span class="mi">8080</span><span class="o">]</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="ss">url</span><span class="p">:</span> <span class="o">[</span><span class="ss">host</span><span class="p">:</span> <span class="s2">&#34;your-app-name.yourdomain.com&#34;</span><span class="p">,</span> <span class="ss">port</span><span class="p">:</span> <span class="mi">80</span><span class="o">]</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="n">config</span> <span class="ss">:phoenix</span><span class="p">,</span> <span class="ss">:serve_endpoints</span><span class="p">,</span> <span class="kp">true</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Você PRECISA deixar fixo o PORT padrão do servidor web Phoenix e o domínio permitido (lembra do nome de domínio que você associou ao seu servidor HAProxy acima? Esse mesmo). E PRECISA descomentar a linha <code>:serve_endpoints, true</code>!</p>
<p>Para o edeliver funcionar, você precisa criar um arquivo <code>.deliver/config</code> assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>USING_DISTILLERY=true

# change this to your app name:
APP=&#34;your-app-name&#34;

# change this to your own servers IP and add as many as you want
US=&#34;nyc-ip-address&#34;
UK=&#34;lon-ip-address&#34;

# the user you created in your Ubuntu machines above
USER=&#34;pusher&#34;

# which server do you want to build the first release?
BUILD_HOST=$US
BUILD_USER=$USER
BUILD_AT=&#34;/tmp/edeliver/$APP/builds&#34;

# list the production servers declared above:
PRODUCTION_HOSTS=&#34;$US $UK&#34;
PRODUCTION_USER=$USER
DELIVER_TO=&#34;/home/$USER&#34;

# do not change here

LINK_VM_ARGS=&#34;/home/$USER/vm.args&#34;

# For *Phoenix* projects, symlink prod.secret.exs to our tmp source
pre_erlang_get_and_update_deps() {
  local _prod_secret_path=&#34;/home/$USER/prod.secret.exs&#34;
  if [ &#34;$TARGET_MIX_ENV&#34; = &#34;prod&#34; ]; then
    __sync_remote &#34;
      ln -sfn &#39;$_prod_secret_path&#39; &#39;$BUILD_AT/config/prod.secret.exs&#39;

      cd &#39;$BUILD_AT&#39;

      mkdir -p priv/static

      mix deps.get

      npm install

      brunch build --production

      APP=&#39;$APP&#39; MIX_ENV=&#39;$TARGET_MIX_ENV&#39; $MIX_CMD phoenix.digest $SILENCE
    &#34;
  fi
}</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Lembra de todas as informações que fomos juntando desde o início desta longa receita? São essas opções que você PRECISA mudar para as suas. Basta seguir os comentários no conteúdo do arquivo acima e adicionar ao seu repositório git. Aliás, seu projeto está num repositório GIT adequado, né??</p>
<p>Se você gosta de usar chaves SSH privadas protegidas por passphrase, vai ser uma dor de cabeça enorme para fazer deploy porque, para cada comando, o edeliver vai emitir um comando SSH que vai ficar pedindo sua passphrase, umas dez vezes ao longo de todo o processo. Você foi avisado! Se ainda assim não se importar, e estiver num Mac, vai ter um problema extra porque o Terminal não consegue criar um prompt para você digitar sua passphrase. Você precisa criar um <a href="https://github.com/markcarver/mac-ssh-askpass"target="_blank" rel="noopener">script</a> <code>/usr/local/bin/ssh-askpass</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>#!/bin/bash
# Script: ssh-askpass
# Author: Mark Carver
# Created: 2011-09-14
# Licensed under GPL 3.0

# A ssh-askpass command for Mac OS X
# Based from author: Joseph Mocker, Sun Microsystems
# http://blogs.oracle.com/mock/entry/and_now_chicken_of_the

# To use this script:
#   Install this script running INSTALL as root
#
# If you plan on manually installing this script, please note that you will have
# to set the following variable for SSH to recognize where the script is located:
#   export SSH_ASKPASS=&#34;/path/to/ssh-askpass&#34;

TITLE=&#34;${SSH_ASKPASS_TITLE:-SSH}&#34;;
TEXT=&#34;$(whoami)&#39;s password:&#34;;
IFS=$(printf &#34;\n&#34;);
CODE=(&#34;on GetCurrentApp()&#34;);
CODE=(${CODE[*]} &#34;tell application \&#34;System Events\&#34; to get short name of first process whose frontmost is true&#34;);
CODE=(${CODE[*]} &#34;end GetCurrentApp&#34;);
CODE=(${CODE[*]} &#34;tell application GetCurrentApp()&#34;);
CODE=(${CODE[*]} &#34;activate&#34;);
CODE=(${CODE[*]} &#34;display dialog \&#34;${@:-$TEXT}\&#34; default answer \&#34;\&#34; with title \&#34;${TITLE}\&#34; with icon caution with hidden answer&#34;);
CODE=(${CODE[*]} &#34;text returned of result&#34;);
CODE=(${CODE[*]} &#34;end tell&#34;);
SCRIPT=&#34;/usr/bin/osascript&#34;
for LINE in ${CODE[*]}; do
      SCRIPT=&#34;${SCRIPT} -e $(printf &#34;%q&#34; &#34;${LINE}&#34;)&#34;;
done;
eval &#34;${SCRIPT}&#34;;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora faça isso:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo chmod &#43;x /usr/local/bin/ssh-askpass
sudo ln -s /usr/local/bin/ssh-askpass /usr/X11R6/bin/ssh-askpass</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Lembre-se, apenas para Macs. E agora toda vez que tentar fazer deploy você vai receber vários prompts gráficos pedindo a passphrase da chave privada SSH. É de enlouquecer! E você precisa ter o <a href="https://www.xquartz.org/"target="_blank" rel="noopener">XQuartz</a> instalado, por sinal.</p>
<p>Agora você precisa criar manualmente 3 arquivos em todos os servidores Phoenix. Comece com <code>your-app-name/vm.args</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>-name us@nyc-ip-address
-setcookie @bCd&amp;fG
-kernel inet_dist_listen_min 9100 inet_dist_listen_max 9155
-config /home/pusher/your-app-name.config
-smp auto</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O <code>/home/pusher/your-app-name</code> é o diretório onde a release vai ser descomprimida depois que o edeliver fizer o deploy do tarball.</p>
<p>Você precisa criar este arquivo em todas as máquinas Phoenix, mudando o bit <code>-name</code> pelo mesmo nome que declarou no arquivo <code>.deliver/config</code>. O <code>-setcookie</code> pode ser qualquer nome, desde que seja o mesmo em todos os servidores.</p>
<p>Viu o <code>-config /home/pusher/your-app-name.config</code>? Crie esse arquivo com o seguinte:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>[{kernel,
  [
    {sync_nodes_optional, [&#39;uk@lon-ip-address&#39;]},
    {sync_nodes_timeout, 30000}
  ]}
].</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Isso é código-fonte Erlang. Na máquina de NYC você deve declarar o nome de Londres, e vice-versa. Se você tiver várias máquinas, todas elas exceto a que você está agora. Entendeu?</p>
<p>Por fim, para a própria app Phoenix, você sempre tem um <code>config/prod.secret.exs</code> que nunca deve ser adicionado com <code>git add</code> ao repositório, lembra? É aqui que você coloca as informações do servidor Postgresql e a chave secreta aleatória para assinar os cookies de sessão:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>use Mix.Config

config :your_app_name, YourAppName.Endpoint,
  secret_key_base: &#34;...&#34;

# Configure your database
config :your_app_name, YourAppName.Repo,
  adapter: Ecto.Adapters.Postgres,
  username: &#34;pusher&#34;,
  password: &#34;your-super-secure-pg-password&#34;,
  database: &#34;your-app-database-name&#34;,
  hostname: &#34;pg-ip-address&#34;,
  pool_size: 20

# if you have Guardian, for example:
config :guardian, Guardian,
  secret_key: &#34;...&#34;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Como você cria uma nova chave secreta aleatória? Na sua máquina de desenvolvimento, basta rodar: <code>mix phoenix.gen.secret</code> e copiar a string gerada para o arquivo acima.</p>
<p>Então agora você deve ter esses 3 arquivos em cada servidor Phoenix, na pasta home <code>/home/pusher</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>~/your-app-name/vm.args
~/prod.secret.exs
~/your-app-name.config</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Conforme a <a href="https://hexdocs.pm/distillery/runtime-configuration.html#vm-args"target="_blank" rel="noopener">documentação do distillery</a>, você precisa definir uma variável de ambiente para informar a localização do arquivo <code>vm.args</code>, e isso é <strong>SUPER</strong> importante - caso contrário ele vai gerar um padrão que não define o nome e cookie corretos, então os nodes não vão se encontrar depois de subir.</p>
<p>Usando <code>sudo</code>, edite o <code>/etc/environment</code> e adicione a linha:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>RELEASE_CONFIG_DIR=/home/pusher/your-app-name
VMARGS_PATH=/home/pusher/your-app-name/vm.args</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Faça isso em todos os servidores phoenix.</p>
<p>No seu diretório de desenvolvimento local você ainda precisa rodar isso:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>mix release.init</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Isso vai gerar um arquivo padrão <code>rel/config.exs</code> que você precisa adicionar ao repositório git com as seguintes alterações no final:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="n">environment</span> <span class="ss">:prod</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">plugin</span> <span class="no">Releases</span><span class="o">.</span><span class="n">Plugin</span><span class="o">.</span><span class="n">LinkConfig</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">release</span> <span class="ss">:your_app_name</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">set</span> <span class="ss">version</span><span class="p">:</span> <span class="n">current_version</span><span class="p">(</span><span class="ss">:your_app_name</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">set</span> <span class="ss">applications</span><span class="p">:</span> <span class="o">[</span>
</span></span><span class="line"><span class="cl">    <span class="ss">your_app_name</span><span class="p">:</span> <span class="ss">:permanent</span>
</span></span><span class="line"><span class="cl">  <span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O plugin deve habilitar a release a encontrar o arquivo <code>vm.args</code> local no servidor (isso é super importante, caso contrário os comandos remotos como start, stop, ping, etc. não vão funcionar e os nodes não vão carregar as informações corretas para subir e formar um cluster).</p>
<p>Finalmente, tudo pronto, você pode executar este comando:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>$ mix edeliver build release production --skip-mix-clean --verbose</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Se terminar sem erros você vai ter uma mensagem parecida com esta no final:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>...
==&gt; Release successfully built!
    You can run it in one of the following ways:
      Interactive: _build/prod/rel/your-app-name/bin/your-app-name console
      Foreground: _build/prod/rel/your-app-name/bin/your-app-name foreground
      Daemon: _build/prod/rel/your-app-name/bin/your-app-name start
--&gt; Copying release 0.0.1 to local release store
--&gt; Copying your-app-name.tar.gz to release store

RELEASE BUILD OF YOUR-APP-NAME WAS SUCCESSFUL!</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora, isso vai demorar um tempo absurdamente longo para fazer o deploy. Isso porque vai clonar o código-fonte da sua app com git, buscar todas as dependências Elixir (toda vez!), compilar tudo, depois rodar o lentíssimo <code>npm install</code> (toda vez!), processar os assets com brunch, criar a tal &ldquo;release&rdquo;, comprimir em tar e gzip, baixar e enviar via SCP para as outras máquinas configuradas.</p>
<p>No arquivo <code>.deliver/config</code> você definiu a opção <code>BUILD_HOST</code>. É a máquina onde todo esse processo acontece, então você vai querer que pelo menos essa máquina seja mais robusta que as outras. Como estou usando droplets pequenos de 512Mb, o processo leva uma eternidade.</p>
<p>O último comando vai baixar o tarball gerado. Ele precisa fazer isso para garantir que os <a href="http://erlang.org/doc/tutorial/nif.html"target="_blank" rel="noopener">NIFs</a> estejam compilados no ambiente nativo onde vai rodar, porque se você usa Mac, binários de Mac não vão rodar no Linux.</p>
<p>Agora precisamos fazer o upload e descompressão desse tarball em cada servidor com o seguinte comando:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>mix edeliver deploy release to production</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Quanto mais lenta sua rede, mais tempo vai levar, já que está enviando um tarfile grande pela internet pública, então certifique-se de estar numa conexão rápida. Quando terminar, podemos reiniciar os servidores (se você já tinha instâncias rodando):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>mix edeliver stop production
mix edeliver start production</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Se você fizer tudo certo, o processo do edeliver termina sem erros e deixa um daemon rodando no seu servidor, assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>/home/pusher/your-app-name/erts-8.2/bin/beam -- -root /home/pusher/your-app-name -progname home/pusher/your-app-name/releases/0.0.1/your-app-name.sh -- -home /home/pusher -- -boot /home/pusher/your-app-name/releases/0.0.1/your-app-name -config /home/pusher/your-app-name/running-config/sys.config -boot_var ERTS_LIB_DIR /home/pusher/your-app-name/erts-8.2/../lib -pa /home/pusher/your-app-name/lib/your-app-name-0.0.1/consolidated -name us@nyc-ip-address -setcookie ex-push&amp;r-l!te -kernel inet_dist_listen_min 9100 inet_dist_listen_max 9155 -config /home/pusher/your-app-name.config -mode embedded -user Elixir.IEx.CLI -extra --no-halt &#43;iex -- console</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Ainda vai demorar bastante, mas deve ser mais fácil. Então aqui vai uma dica pro para usuários Linux. Siga <a href="https://gist.github.com/mattweldon/2e8ecb953216438ad168"target="_blank" rel="noopener">este Gist</a> para mais detalhes; você precisa emular o que é executado na segunda metade do arquivo <code>.deliver/config</code>.</p>
<p>Note também que rodei as migrations manualmente, mas você pode fazer isso usando <code>mix edeliver migrate</code>.</p>
<p>Leia a documentação deles para mais comandos e configurações.</p>
<p>E não esqueça de habilitar o UFW:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo ufw allow ssh
sudo ufw allow 8080
sudo ufw allow 4369
sudo ufw allow proto tcp from any to any port 9100:9155
sudo ufw default allow outgoing
sudo ufw enable</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<h3>Depurando bugs em produção<span class="hx:absolute hx:-mt-20" id="depurando-bugs-em-produção"></span>
    <a href="#depurando-bugs-em-produ%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Logo depois que fiz o deploy, obviamente falhou. E o problema é que os arquivos <code>/home/pusher/your-app-name/log/erlang.log</code> (eles são rotacionados automaticamente, então você pode encontrar vários arquivos terminando em número) não vão mostrar muita coisa.</p>
<p>O que eu recomendo é alterar o arquivo <code>config/prod.exs</code> APENAS na sua máquina de desenvolvimento e mudar o nível de log para <code>config :logger, level: :debug</code>, usar o mesmo <code>prod.secret.exs</code> que você editou nos servidores e rodar localmente com <code>MIX_ENV=prod iex -S mix phoenix.server</code>.</p>
<p>Por exemplo, em modo de desenvolvimento eu tinha um código no controller que verificava a existência de um parâmetro de query string opcional assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">if</span> <span class="n">params</span><span class="o">[</span><span class="s2">&#34;some_parameter&#34;</span><span class="o">]</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl"> <span class="o">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Funcionava bem em desenvolvimento mas travava em produção, então tive que mudar para:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">if</span> <span class="no">Map</span><span class="o">.</span><span class="n">has_key?</span><span class="p">(</span><span class="n">params</span><span class="p">,</span> <span class="s2">&#34;some_parameter&#34;</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl"> <span class="o">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Outro problema foi que Guardian funcionava normalmente em desenvolvimento, mas em produção precisei declarar sua aplicação no <code>mix.exs</code> assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">application</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="o">[</span><span class="ss">mod</span><span class="p">:</span> <span class="p">{</span><span class="no">ExPusherLite</span><span class="p">,</span> <span class="o">[]</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">   <span class="ss">applications</span><span class="p">:</span> <span class="o">[...</span><span class="p">,</span> <span class="ss">:guardian</span><span class="p">,</span> <span class="ss">:edeliver</span><span class="o">]]</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Estava recebendo erros <code>:econnrefused</code> porque esqueci de rodar <code>MIX_ENV=prod mix do ecto.create, ecto.migrate</code> como instruí acima. Depois de resolver essas questões, minha aplicação estava funcionando em <code>http://your-app-name.yourdomain.com</code>, o HAProxy estava encaminhando corretamente para a porta 8080 nos servidores e tudo roda bem, incluindo as conexões WebSocket.</p>
<h3>Conclusão<span class="hx:absolute hx:-mt-20" id="conclusão"></span>
    <a href="#conclus%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Como mencionei acima, esse tipo de procedimento me faz sentir falta de uma solução de deploy fácil como o Heroku.</p>
<p>O único problema que estou enfrentando agora é que quando faço login pela página de sign in do Coherence, não sou redirecionado para o URI correto que estava tentando acessar (&quot;/admin&quot; no meu caso) - às vezes recarregar depois do login funciona, às vezes não. Às vezes estou dentro de uma página &ldquo;/admin&rdquo; mas quando clico em um dos links ele me manda de volta para a página de login mesmo estando logado. Não sei se é um bug no Coherence, ExAdmin, no próprio Phoenix ou uma configuração errada do HAProxy. Vou atualizar este post se descobrir.</p>
<p>O Edeliver também leva um tempo obsceno para fazer deploy. Até esperar os sprockets processarem num <code>git push heroku master</code> parece muito mais rápido em comparação. E isso para uma app Phoenix bem enxuta. Ter que buscar tudo (porque o Hex não mantém um cache global local, todas as dependências são vendorizadas estaticamente no diretório do projeto) e ter que rodar o lentíssimo npm não ajuda em nada.</p>
<p>Ainda preciso pesquisar se há opções mais rápidas, mas por ora o que tenho &ldquo;funciona&rdquo;.</p>
<p>E mais importante: agora tenho um cluster escalável para WebSockets bidirecionais em tempo real, que é a principal razão pela qual alguém pode querer usar Phoenix.</p>
<p>Se você quer construir um site &ldquo;normal&rdquo;, mantenha simples e faça em Rails, Django, Express ou qual for seu framework web preferido. Se você quer comunicações em tempo real de forma fácil, talvez eu tenha uma solução melhor. Fique de olho no blog para novidades que vêm por aí! ;-)</p>
]]></content:encoded><category>elixir-e-erlang</category><category>devops</category><category>cloud</category><category>sistemas-distribuidos</category></item><item><title>[Manga-Downloadr] Melhorando o Crystal/Ruby de rajadas para fluxo de pool</title><link>https://akitaonrails.github.io/2016/06/07/manga-downloadr-melhorando-o-crystal-ruby-de-rajadas-para-fluxo-de-pool/</link><guid isPermaLink="true">https://akitaonrails.github.io/2016/06/07/manga-downloadr-melhorando-o-crystal-ruby-de-rajadas-para-fluxo-de-pool/</guid><pubDate>Tue, 07 Jun 2016 17:13:00 -0300</pubDate><description>&lt;p&gt;Ontem &lt;a href="http://www.akitaonrails.com/2016/06/06/manga-downloadr-portando-de-crystal-para-ruby-e-um-pouco-de-jruby"target="_blank" rel="noopener"&gt;eu publiquei&lt;/a&gt; como construí uma nova implementação do Manga Reader Downloader em Crystal, portado para Ruby, testado em JRuby e comparado com Elixir. Só pra recapitular, esses foram os resultados pra buscar capítulos, páginas e links de imagens de um mangá de exemplo:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MRI Ruby 2.3.1: 57 segundos&lt;/li&gt;
&lt;li&gt;JRuby 9.1.1.0: 45 segundos&lt;/li&gt;
&lt;li&gt;Crystal 0.17.0: 59 segundos&lt;/li&gt;
&lt;li&gt;Elixir: 14 segundos&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Eu também disse que era uma comparação injusta, já que a versão Elixir usa um algoritmo diferente - e obviamente mais eficiente.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Ontem <a href="http://www.akitaonrails.com/2016/06/06/manga-downloadr-portando-de-crystal-para-ruby-e-um-pouco-de-jruby"target="_blank" rel="noopener">eu publiquei</a> como construí uma nova implementação do Manga Reader Downloader em Crystal, portado para Ruby, testado em JRuby e comparado com Elixir. Só pra recapitular, esses foram os resultados pra buscar capítulos, páginas e links de imagens de um mangá de exemplo:</p>
<ul>
<li>MRI Ruby 2.3.1: 57 segundos</li>
<li>JRuby 9.1.1.0: 45 segundos</li>
<li>Crystal 0.17.0: 59 segundos</li>
<li>Elixir: 14 segundos</li>
</ul>
<p>Eu também disse que era uma comparação injusta, já que a versão Elixir usa um algoritmo diferente - e obviamente mais eficiente.</p>
<p>Era a &ldquo;primeira-versão-que-funcionou&rdquo;, então resolvi ir adiante e melhorar as implementações. Na versão Ruby/JRuby adicionei a gem <a href="https://github.com/meh/ruby-thread"target="_blank" rel="noopener">thread</a> pra ter uma implementação razoável de Thread pool que funciona tanto em Ruby quanto em JRuby. Eu provavelmente deveria ter usado Concurrent-Ruby, mas estava tendo problemas pra fazer o FixedThreadPool funcionar.</p>
<p>Enfim, agora todas as versões vão ter um pool constante de requisições rodando e podemos fazer uma comparação melhor.</p>
<p>Outra coisa que pode ter prejudicado os resultados do Crystal é que ele parece ter uma <a href="https://github.com/crystal-lang/crystal/issues/2660"target="_blank" rel="noopener">implementação defeituosa de DNS resolver</a>, então por enquanto eu só adicionei o IP do Manga Reader direto no meu <code>/etc/hosts</code> pra evitar exceções de <code>getaddrinfo: Name or service not known</code>.</p>
<p>Pra começar, vamos testar a mesma implementação Elixir e, como esperado, o resultado continua o mesmo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>11,49s user 0,82s system 77% cpu 15,827 total</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora, o MRI Ruby com o algoritmo de &ldquo;rajada em lote&rdquo; estava levando 57 segundos e essa nova implementação usando ThreadPool roda muito melhor:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>12,67s user 0,92s system 50% cpu 27,149 total</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Em Crystal é um pouco mais complicado, porque não tem como implementar o equivalente a um &ldquo;Fiber Pool&rdquo;. O que temos que fazer é um loop infinito até o último processo sinalizar a quebra do loop. Dentro do loop criamos um número máximo de fibers, esperamos cada uma sinalizar que terminou através de um channel individual e voltamos ao loop pra criar uma nova fiber, e por aí vai. Comparado aos 59 segundos de ontem, isso é bem melhor:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>5,29s user 0,33s system 26% cpu 21,166 total</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>A versão JRuby não está tão rápida. Ainda melhor que os 45 segundos de ontem, mas agora está perdendo até pro MRI:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>49,24s user 1,41s system 146% cpu 34,602 total</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Tentei usar a flag <code>--dev</code> pra <a href="https://github.com/jruby/jruby/wiki/Improving-startup-time"target="_blank" rel="noopener">tempo de inicialização mais rápido</a> e melhora um pouco, ficando mais perto do MRI:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>22,26s user 0,99s system 76% cpu 30,320 total</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Não sei se dá pra melhorar mais, mas qualquer dica é bem-vinda - não esqueçam de comentar abaixo.</p>
<p>Então, Elixir continua pelo menos duas vezes mais rápido que Crystal nesse momento. Mas isso também demonstra como um algoritmo diferente faz uma diferença enorme onde importa. Eu provavelmente consigo ajustar mais um pouco, mas isso já basta por enquanto.</p>
<h3>Mudando a implementação Ruby pra usar ThreadPool<span class="hx:absolute hx:-mt-20" id="mudando-a-implementação-ruby-pra-usar-threadpool"></span>
    <a href="#mudando-a-implementa%c3%a7%c3%a3o-ruby-pra-usar-threadpool" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="nb">require</span> <span class="s2">&#34;thread/pool&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">module</span> <span class="nn">MangaDownloadr</span>
</span></span><span class="line"><span class="cl">  <span class="k">class</span> <span class="nc">Concurrency</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">fetch</span><span class="p">(</span><span class="n">collection</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">block</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="n">pool</span>    <span class="o">=</span> <span class="no">Thread</span><span class="o">.</span><span class="n">pool</span><span class="p">(</span><span class="vi">@config</span><span class="o">.</span><span class="n">download_batch_size</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="n">mutex</span>   <span class="o">=</span> <span class="no">Mutex</span><span class="o">.</span><span class="n">new</span>
</span></span><span class="line"><span class="cl">      <span class="n">results</span> <span class="o">=</span> <span class="o">[]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">collection</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">item</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">        <span class="n">pool</span><span class="o">.</span><span class="n">process</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="n">engine</span>  <span class="o">=</span> <span class="vi">@turn_on_engine</span> <span class="p">?</span> <span class="vi">@engine_klass</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="vi">@config</span><span class="o">.</span><span class="n">domain</span><span class="p">)</span> <span class="p">:</span> <span class="kp">nil</span>
</span></span><span class="line"><span class="cl">          <span class="n">reply</span> <span class="o">=</span> <span class="n">block</span><span class="o">.</span><span class="n">call</span><span class="p">(</span><span class="n">item</span><span class="p">,</span> <span class="n">engine</span><span class="p">)</span><span class="o">&amp;.</span><span class="n">flatten</span>
</span></span><span class="line"><span class="cl">          <span class="n">mutex</span><span class="o">.</span><span class="n">synchronize</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">            <span class="n">results</span> <span class="o">+=</span> <span class="p">(</span> <span class="n">reply</span> <span class="o">||</span> <span class="o">[]</span> <span class="p">)</span>
</span></span><span class="line"><span class="cl">          <span class="k">end</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="k">end</span>
</span></span><span class="line"><span class="cl">      <span class="n">pool</span><span class="o">.</span><span class="n">shutdown</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">results</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>A ideia é que vamos ter um número fixo de threads nativas spawnadas (nesse caso, determinado por <code>@config.download_batch_size</code>). Conforme uma thread termina, ela puxa um novo link do Array <code>collection</code>, funcionando essencialmente como uma &ldquo;fila&rdquo; a ser esvaziada.</p>
<p>Os resultados são acumulados no Array <code>results</code>. Como várias threads podem querer modificá-lo ao mesmo tempo, temos que sincronizar o acesso através de um Mutex.</p>
<p>Dessa forma sempre temos uma quantidade fixa de workers fazendo requisições constantemente em vez de fatiar o Array <code>collection</code> e fazer rajadas como na versão anterior.</p>
<h3>Mudando a implementação Crystal pra simular um Pool de Fibers<span class="hx:absolute hx:-mt-20" id="mudando-a-implementação-crystal-pra-simular-um-pool-de-fibers"></span>
    <a href="#mudando-a-implementa%c3%a7%c3%a3o-crystal-pra-simular-um-pool-de-fibers" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A versão Crystal ficou um pouco mais complicada porque não achei nenhuma biblioteca de pool pra usar. Encontrei uma implementação rústica de pool <a href="http://stackoverflow.com/a/30854065/1529907"target="_blank" rel="noopener">neste post do stackoverflow</a> e consegui implementar uma versão melhorada num shard novo, pra que vocês possam aproveitar nos seus projetos. Confiram o código fonte em <a href="https://github.com/akitaonrails/fiberpool"target="_blank" rel="noopener">akitaonrails/fiberpool</a>. Foi assim que adicionei no meu projeto:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">dependencies</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="l">...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">fiberpool</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">github</span><span class="p">:</span><span class="w"> </span><span class="l">akitaonrails/fiberpool</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E foi assim que usei. Repare que a lógica em si é bem próxima da versão MRI Ruby, mas usando um &ldquo;Fiber Pool&rdquo; em vez de Thread Pool.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="nb">require</span> <span class="s2">&#34;fiberpool&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">module</span> <span class="nn">CrMangaDownloadr</span>
</span></span><span class="line"><span class="cl">  <span class="n">struct</span> <span class="no">Concurrency</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="vi">@config</span> <span class="p">:</span> <span class="no">Config</span><span class="p">,</span> <span class="vi">@turn_on_engine</span> <span class="o">=</span> <span class="kp">true</span><span class="p">);</span> <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">fetch</span><span class="p">(</span><span class="n">collection</span> <span class="p">:</span> <span class="nb">Array</span><span class="p">(</span><span class="n">A</span><span class="p">)</span><span class="sc">?,</span> <span class="n">engine_class</span> <span class="p">:</span> <span class="n">E</span><span class="o">.</span><span class="n">class</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">block</span> <span class="p">:</span> <span class="n">A</span><span class="p">,</span> <span class="n">E?</span> <span class="o">-&gt;</span> <span class="nb">Array</span><span class="p">(</span><span class="n">B</span><span class="p">)</span><span class="sc">?)</span> <span class="p">:</span> <span class="nb">Array</span><span class="p">(</span><span class="n">B</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="n">results</span> <span class="o">=</span> <span class="o">[]</span> <span class="n">of</span> <span class="n">B</span>
</span></span><span class="line"><span class="cl">      <span class="k">if</span> <span class="n">collection</span>
</span></span><span class="line"><span class="cl">        <span class="n">pool</span> <span class="o">=</span> <span class="no">Fiberpool</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">collection</span><span class="p">,</span> <span class="vi">@config</span><span class="o">.</span><span class="n">download_batch_size</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">pool</span><span class="o">.</span><span class="n">run</span> <span class="k">do</span> <span class="o">|</span><span class="n">item</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">          <span class="n">engine</span> <span class="o">=</span> <span class="k">if</span> <span class="vi">@turn_on_engine</span>
</span></span><span class="line"><span class="cl">                     <span class="n">engine_class</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="vi">@config</span><span class="o">.</span><span class="n">domain</span><span class="p">,</span> <span class="vi">@config</span><span class="o">.</span><span class="n">cache_http</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                   <span class="k">end</span>
</span></span><span class="line"><span class="cl">          <span class="k">if</span> <span class="n">reply</span> <span class="o">=</span> <span class="n">block</span><span class="o">.</span><span class="n">call</span><span class="p">(</span><span class="n">item</span><span class="p">,</span> <span class="n">engine</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">results</span><span class="o">.</span><span class="n">concat</span><span class="p">(</span><span class="n">reply</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">          <span class="k">end</span>
</span></span><span class="line"><span class="cl">        <span class="k">end</span>
</span></span><span class="line"><span class="cl">      <span class="k">end</span>
</span></span><span class="line"><span class="cl">      <span class="n">results</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Mas, de novo, isso é muito intensivo em I/O e tanto a versão Ruby quanto a Crystal aproveitam o fato de poderem fazer mais trabalho enquanto esperam uma requisição HTTP terminar.</p>
<p><a name="reproducing-tests"> </a></p>
<h3>Reproduzindo os Testes<span class="hx:absolute hx:-mt-20" id="reproduzindo-os-testes"></span>
    <a href="#reproduzindo-os-testes" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Implementei um &ldquo;Modo de Teste&rdquo; nas 3 implementações. Vocês podem clonar dos meus repositórios:</p>
<ul>
<li><a href="https://github.com/akitaonrails/cr_manga_downloadr"target="_blank" rel="noopener">Versão Crystal</a></li>
<li><a href="https://github.com/akitaonrails/manga-downloadr"target="_blank" rel="noopener">Versão Ruby/JRuby</a></li>
<li><a href="https://github.com/akitaonrails/ex_manga_downloadr"target="_blank" rel="noopener">Versão Elixir</a></li>
</ul>
<p>E vocês podem rodar o modo de teste assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># crystal:
time ./cr_manga_downloadr --test

# MRI:
time bin/manga-downloadr --test

# JRuby (você precisa editar o Gemfile pra descomentar a engine JRuby):
time jruby --dev -S bin/manga-downloadr --test

# Elixir:
time ./ex_manga_downloadr --test</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Isso vai rodar só o fetch de capítulos, páginas e links de imagens, pulando o download real das imagens, otimização via mogrify e compilação do PDF. Essas partes puladas demoram demais e não dizem nada sobre as linguagens testadas.</p>
<p>E se quiserem testar só as partes intensivas em CPU e evitar qualquer interferência de rede, vocês podem ligar o modo HTTP Cache e rodar os testes duas vezes pra que a primeira execução cacheie tudo, assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># crystal:
time ./cr_manga_downloadr --test --cache

# MRI:
time bin/manga-downloadr --test --cache

# JRuby (você precisa editar o Gemfile pra descomentar a engine JRuby):
time jruby --dev -S bin/manga-downloadr --test --cache

# Elixir:
time CACHE_HTTP=true ./ex_manga_downloadr --test</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Então, <strong>com todas as requisições já cacheadas</strong> esses são os resultados:</p>
<p>Elixir:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>7,13s user 0,24s system 331% cpu 2,227 total</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>MRI Ruby:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>5.590000   0.180000   5.770000 (  5.678714)
5,87s user 0,21s system 101% cpu 5,996 total</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>JRuby:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>10.580000   0.180000  10.760000 (  3.184472)
14,54s user 0,44s system 262% cpu 5,716 total</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Crystal:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>1.610000   0.050000   1.660000 (  1.344123)
1,62s user 0,06s system 124% cpu 1,350 total</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>As versões Ruby/JRuby/Crystal têm benchmarks internos pra remover o tempo de startup (por isso elas têm 2 linhas de tempos).</p>
<p>Então, Elixir é muito rápido. Leva mais ou menos 2 segundos pra parsear todos os 1.900 arquivos HTML procurando os links.</p>
<p>Ruby é o mais lento, obviamente. Leva quase 6 segundos.</p>
<p>A versão JRuby também leva quase 6 segundos, mas internamente processa em 3 segundos; o resto é tempo de startup e warm up da JVM por baixo.</p>
<p>E Crystal é o mais rápido, como vocês esperariam, porque é um binário super otimizado fazendo operações CPU bound, marcando pouco mais de 1 segundo.</p>
<h3>Conclusão<span class="hx:absolute hx:-mt-20" id="conclusão"></span>
    <a href="#conclus%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Mesmo com algoritmos mais ou menos parecidos, Elixir continua ganhando por uma margem muito grande no processo total (com as requisições HTTP externas).</p>
<p>Tem mais coisa em jogo além de velocidade de interpretador/compilador, além de infraestrutura de single-thread, multi-thread e fibers.</p>
<p>Também temos a maturidade das respectivas bibliotecas padrão (incluindo stack TCP, bibliotecas HTTP client, operações de String/Array/Regex, etc.) e bibliotecas de terceiros (libXML, Nokogiri, etc.). Então tem muita coisa que pode interferir nos testes. Eu chutaria que a stdlib do Crystal, especialmente as partes de rede, ainda não está bem testada em batalha nesse momento (pré 1.0!).</p>
<p>Então, o resumo com os novos resultados é esse:</p>
<ul>
<li>MRI Ruby 2.3.1: 27 segundos</li>
<li>JRuby 9.1.1.0: 30 segundos</li>
<li>Crystal 0.17.0: 21 segundos</li>
<li>Elixir: 15 segundos</li>
</ul>
<p>Me avisem se tiverem ideias pra deixar tudo ainda mais rápido!</p>
]]></content:encoded><category>anime-e-manga</category><category>performance</category><category>sistemas-distribuidos</category></item><item><title>Melhorando a Performance de Integração entre Microservices com Memcache e ETAGs</title><link>https://akitaonrails.github.io/2016/03/23/melhorando-performance-integracao-microservices-memcache-etags/</link><guid isPermaLink="true">https://akitaonrails.github.io/2016/03/23/melhorando-performance-integracao-microservices-memcache-etags/</guid><pubDate>Wed, 23 Mar 2016 14:46:00 -0300</pubDate><description>&lt;p&gt;Todo mundo está embarcando em microservices. Não tem como fugir. No mundo Rails estamos bem equipados para satisfazer a fome de qualquer Framework Javascript da moda por consumo de APIs.&lt;/p&gt;
&lt;p&gt;Resumindo, a maioria das pessoas está apenas expondo seu conteúdo via endpoints JSON simples e consumindo eles a partir de outros microservices via simples HTTP GETs. Quanto mais microservices você adiciona na cadeia, mais tempo o último endpoint demora para retornar. Existem várias técnicas para melhorar essa situação, mas eu quero mostrar apenas uma bem simples que resolve muitas situações comuns sem muito trabalho.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Todo mundo está embarcando em microservices. Não tem como fugir. No mundo Rails estamos bem equipados para satisfazer a fome de qualquer Framework Javascript da moda por consumo de APIs.</p>
<p>Resumindo, a maioria das pessoas está apenas expondo seu conteúdo via endpoints JSON simples e consumindo eles a partir de outros microservices via simples HTTP GETs. Quanto mais microservices você adiciona na cadeia, mais tempo o último endpoint demora para retornar. Existem várias técnicas para melhorar essa situação, mas eu quero mostrar apenas uma bem simples que resolve muitas situações comuns sem muito trabalho.</p>
<p>Antes de tudo, se você está lidando com cache, nunca tente expirar entradas de cache. A coisa mais importante para aprender sobre caching é como gerar chaves de cache adequadas. Faça isso direito e a maior parte dos problemas com cache desaparecem.</p>
<p>Segundo, se você está usando HTTP, tente usar tudo o que você puder dele, ao invés de reinventar a roda.</p>
<p>A versão &ldquo;TL;DR&rdquo; é: faça suas APIs retornarem ETAGs adequados e tratarem corretamente os headers &ldquo;If-None-Match&rdquo;, retornando o status code 304 ao invés de 200 com o body completo da resposta toda vez. E na ponta consumidora, faça cache do ETAG junto com o body correspondente e use ele do cache quando receber 304s. Você vai economizar pelo menos o tempo caro de renderização na ponta consumida e a banda lenta na ponta consumidora. No final você deve conseguir ficar pelo menos 100% mais rápido, ou mais, com poucos ajustes.</p>
<h3>As Aplicações de Exemplo<span class="hx:absolute hx:-mt-20" id="as-aplicações-de-exemplo"></span>
    <a href="#as-aplica%c3%a7%c3%b5es-de-exemplo" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Num exemplo bem capenga poderíamos ter um controller de API Rails assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1">## 1a aplicação</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Api</span><span class="o">::</span><span class="no">ProductsController</span> <span class="o">&lt;</span> <span class="no">ApplicationController</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">index</span>
</span></span><span class="line"><span class="cl">    <span class="vi">@products</span> <span class="o">=</span> <span class="no">Product</span><span class="o">.</span><span class="n">page</span> <span class="p">(</span><span class="n">params</span><span class="o">[</span><span class="ss">:page</span><span class="o">]</span> <span class="o">||</span> <span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">render</span> <span class="ss">json</span><span class="p">:</span> <span class="o">[</span><span class="ss">num_pages</span><span class="p">:</span> <span class="vi">@products</span><span class="o">.</span><span class="n">num_pages</span><span class="p">,</span> <span class="ss">products</span><span class="p">:</span> <span class="vi">@products</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Para os fins deste exemplo capenga de post, carregamos ele no localhost porta <strong>3001</strong>. Agora, sempre que chamarmos &ldquo;http://localhost:3001/api/products?page=1&rdquo; o servidor da API mostra algo assim no log:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Started GET &#34;/api/products?page=1&#34; for 127.0.0.1 at 2016-03-23 13:29:34 -0300
Processing by Api::ProductsController#index as */*
  Parameters: {&#34;page&#34;=&gt;&#34;1&#34;}
   (0.3ms)  SELECT COUNT(*) FROM &#34;products&#34;
  Product Load (0.9ms)  SELECT  &#34;products&#34;.* FROM &#34;products&#34; LIMIT 100 OFFSET 0
Completed 200 OK in 26ms (Views: 23.0ms | ActiveRecord: 1.2ms)</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Resumindo, está levando cerca de <strong>26ms</strong> para devolver um JSON com a primeira página de produtos desta aplicação. Nada mal.</p>
<p>Daí podemos criar outra aplicação Rails API que consome essa primeira. Algo também bem capenga e bobo assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># 2a aplicação</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Api</span><span class="o">::</span><span class="no">ProductsController</span> <span class="o">&lt;</span> <span class="no">ApplicationController</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">index</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># nunca, jamais, faça hardcode de hostnames assim, use dotenv-rails ou secrets.yml</span>
</span></span><span class="line"><span class="cl">    <span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;http://localhost:3001/api/products?page=?&#34;</span> <span class="o">%</span> <span class="p">(</span><span class="n">params</span><span class="o">[</span><span class="ss">:page</span><span class="o">]</span> <span class="o">||</span> <span class="s2">&#34;1&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">json</span> <span class="o">=</span> <span class="no">Net</span><span class="o">::</span><span class="no">HTTP</span><span class="o">.</span><span class="n">get_response</span><span class="p">(</span><span class="no">URI</span><span class="p">(</span><span class="n">url</span><span class="p">))</span><span class="o">.</span><span class="n">body</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">response</span><span class="o">.</span><span class="n">headers</span><span class="o">[</span><span class="s2">&#34;Content-Type&#34;</span><span class="o">]</span> <span class="o">=</span> <span class="s2">&#34;application/json&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">render</span> <span class="ss">plain</span><span class="p">:</span> <span class="n">json</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Carregamos esta outra app no localhost porta <strong>3000</strong> e quando chamamos &ldquo;http://localhost:3000/api/products?page=1&rdquo; o servidor mostra o seguinte log:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Started GET &#34;/api/products?page=1&#34; for 127.0.0.1 at 2016-03-23 13:31:59 -0300
Processing by Api::ProductsController#index as HTML
  Parameters: {&#34;page&#34;=&gt;&#34;1&#34;}
  Rendered text template (0.0ms)
Completed 200 OK in 51ms (Views: 7.1ms | ActiveRecord: 0.0ms)</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora, esta segunda aplicação está levando o dobro do tempo comparada com a primeira. Podemos assumir que parte desses 51ms são os 26ms da primeira aplicação.</p>
<p>Quanto mais APIs adicionarmos uma em cima da outra, mais tempo o fluxo todo vai levar. 26ms para a primeira, mais 25ms para a segunda, e assim por diante.</p>
<p>Tem várias coisas que poderíamos fazer. Mas eu defendo que devemos começar simples: usando de fato um pouco mais do protocolo HTTP.</p>
<h3>Enviando ETAGs adequados e tratando &ldquo;If-None-Match&rdquo;<span class="hx:absolute hx:-mt-20" id="enviando-etags-adequados-e-tratando-if-none-match"></span>
    <a href="#enviando-etags-adequados-e-tratando-if-none-match" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Em poucas palavras, podemos marcar qualquer resposta HTTP com um ETAG, um identificador para o conteúdo da resposta. Se o ETAG é o mesmo, podemos assumir que o conteúdo não mudou. Browsers recebem os ETAGs e mandam eles de volta se escolhermos atualizar o conteúdo, como um header &ldquo;If-None-Match&rdquo;. Quando um web server recebe esse header, ele compara contra o ETAG da resposta e não devolve nenhum conteúdo, apenas um header HTTP &ldquo;304 Not Modified&rdquo;, que é muito, muito mais leve e rápido de transportar de volta.</p>
<p>Um ETAG pode ser tão complicado quanto um SHA256 hexdigest do conteúdo inteiro da resposta ou tão simples quanto apenas o timestamp &ldquo;updated_at&rdquo; se isso indica que o registro mudou (numa action &ldquo;show&rdquo; de controller, por exemplo). Tem que ser um digest que represente o conteúdo e tem que mudar se o conteúdo mudar.</p>
<p>Rails tem suporte para ETAGs há muito tempo na API <a href="http://api.rubyonrails.org/classes/ActionController/ConditionalGet.html"target="_blank" rel="noopener">ActionController::ConditionalGet</a>.</p>
<p>No nosso exemplo capenga, a 1a aplicação na porta 3001 busca uma página de objetos ActiveRecord e devolve um array representado em formato JSON. Se escolhermos fazer digest do conteúdo final teríamos que deixar a ActionView fazer seu trabalho, mas essa é de longe a operação mais cara, então queremos evitar.</p>
<p>Uma coisa que poderíamos fazer é apenas verificar os campos &ldquo;updated_at&rdquo; de todos os registros e ver se mudaram. Se qualquer um deles mudou, precisaríamos re-renderizar tudo e mandar um novo ETAG e um novo body de resposta. Então o código poderia ser assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Api</span><span class="o">::</span><span class="no">ProductsController</span> <span class="o">&lt;</span> <span class="no">ApplicationController</span>
</span></span><span class="line"><span class="cl">  <span class="n">layout</span> <span class="kp">false</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">index</span>
</span></span><span class="line"><span class="cl">    <span class="vi">@products</span> <span class="o">=</span> <span class="no">Product</span><span class="o">.</span><span class="n">page</span> <span class="p">(</span><span class="n">params</span><span class="o">[</span><span class="ss">:page</span><span class="o">]</span> <span class="o">||</span> <span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">stale?</span><span class="p">(</span><span class="n">freshness</span> <span class="vi">@products</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="n">render</span> <span class="ss">json</span><span class="p">:</span> <span class="o">[</span><span class="ss">num_pages</span><span class="p">:</span> <span class="vi">@products</span><span class="o">.</span><span class="n">num_pages</span><span class="p">,</span> <span class="ss">products</span><span class="p">:</span> <span class="vi">@products</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="kp">private</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">freshness</span><span class="p">(</span><span class="n">collection</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">dates</span> <span class="o">=</span> <span class="n">collection</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="ss">:updated_at</span><span class="p">)</span><span class="o">.</span><span class="n">sort</span>
</span></span><span class="line"><span class="cl">    <span class="n">etag</span> <span class="o">=</span> <span class="n">dates</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="ss">:to_i</span><span class="p">)</span><span class="o">.</span><span class="n">reduce</span><span class="p">(</span><span class="o">&amp;</span><span class="ss">:+</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="ss">etag</span><span class="p">:</span> <span class="no">Digest</span><span class="o">::</span><span class="no">MD5</span><span class="o">.</span><span class="n">hexdigest</span><span class="p">(</span><span class="n">etag</span><span class="o">.</span><span class="n">to_s</span><span class="p">),</span> <span class="ss">last_modified</span><span class="p">:</span> <span class="n">dates</span><span class="o">.</span><span class="n">last</span><span class="p">,</span> <span class="kp">public</span><span class="p">:</span> <span class="kp">true</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora, quando tentamos &ldquo;curl -I http://localhost:3001/api/products?page=1&rdquo; vamos ver os seguintes headers:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>HTTP/1.1 200 OK 
X-Frame-Options: SAMEORIGIN
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Etag: &#34;ccf372c24cd259d0943fa3dc99830b10&#34;
Last-Modified: Wed, 23 Mar 2016 16:25:53 GMT
Content-Type: application/json; charset=utf-8
Cache-Control: public
X-Request-Id: 601f22bc-72a9-4960-97cb-c30a0b56dbf4
X-Runtime: 0.053529
Server: WEBrick/1.3.1 (Ruby/2.3.0/2015-12-25)
Date: Wed, 23 Mar 2016 17:00:13 GMT
Content-Length: 0
Connection: Keep-Alive</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Ótimo! Temos um ETAG que representa unicamente esta página de produtos. Agora podemos ir um passo além e adicionar o seguinte:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># Gemfile</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;dalli&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;rack-cache&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># config/environments/[development|production].rb</span>
</span></span><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="n">config</span><span class="o">.</span><span class="n">cache_store</span> <span class="o">=</span> <span class="ss">:dalli_store</span>
</span></span><span class="line"><span class="cl"><span class="n">client</span> <span class="o">=</span> <span class="no">Dalli</span><span class="o">::</span><span class="no">Client</span><span class="o">.</span><span class="n">new</span>
</span></span><span class="line"><span class="cl"><span class="n">config</span><span class="o">.</span><span class="n">action_dispatch</span><span class="o">.</span><span class="n">rack_cache</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="ss">:metastore</span>    <span class="o">=&gt;</span> <span class="n">client</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="ss">:entitystore</span>  <span class="o">=&gt;</span> <span class="n">client</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="n">config</span><span class="o">.</span><span class="n">static_cache_control</span> <span class="o">=</span> <span class="s2">&#34;public, max-age=2592000&#34;</span>
</span></span><span class="line"><span class="cl"><span class="o">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Esta configuração assume que temos Memcached instalado e rodando na mesma máquina localhost (nosso ambiente de desenvolvimento), mas em produção você pode seguir esta <a href="https://devcenter.heroku.com/articles/rack-cache-memcached-rails31"target="_blank" rel="noopener">boa documentação da Heroku</a>.</p>
<p>Agora, nossa 1a aplicação tem um cache HTTP interno, com o mesmo papel de algo mais avançado como o Varnish na frente dela. Ele vai cachear todas as respostas HTTP 200 da aplicação, junto com os ETAGs. Sempre que uma nova chamada vier para a mesma URI, ele vai checar o cache primeiro, e se a aplicação devolver o mesmo ETAG, ele vai mandar o conteúdo de volta direto do cache.</p>
<p>Então se chamarmos o comando &ldquo;curl&rdquo; acima múltiplas vezes, vamos ver isso no log do servidor Rails:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Started GET &#34;/api/products?page=1&#34; for 127.0.0.1 at 2016-03-23 14:05:16 -0300
Processing by Api::ProductsController#index as */*
  Parameters: {&#34;page&#34;=&gt;&#34;1&#34;}
  Product Load (0.8ms)  SELECT  &#34;products&#34;.* FROM &#34;products&#34; LIMIT 100 OFFSET 0
  Couldn&#39;t find template for digesting: products/index
   (1.0ms)  SELECT COUNT(*) FROM &#34;products&#34;
Completed 200 OK in 31ms (Views: 16.3ms | ActiveRecord: 1.8ms)
cache: [HEAD /api/products?page=1] miss, store</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Repare na última linha: ela diz que tentou achar o ETAG retornado no cache e deu &ldquo;miss&rdquo;, então &ldquo;store&rdquo; do novo conteúdo. Agora, se rodarmos o mesmo comando &ldquo;curl&rdquo; novamente, vamos ver isso:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Started GET &#34;/api/products?page=1&#34; for 127.0.0.1 at 2016-03-23 14:05:59 -0300
Processing by Api::ProductsController#index as */*
  Parameters: {&#34;page&#34;=&gt;&#34;1&#34;}
  Product Load (0.5ms)  SELECT  &#34;products&#34;.* FROM &#34;products&#34; LIMIT 100 OFFSET 0
  Couldn&#39;t find template for digesting: products/index
Completed 304 Not Modified in 12ms (ActiveRecord: 0.5ms)
cache: [HEAD /api/products?page=1] stale, valid, store</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O comando curl simples não está mandando o header &ldquo;If-None-Match&rdquo;, então ele espera receber o body completo da resposta. Mas como temos o Rack Cache ele está adicionando o digest do ETAG do &ldquo;If-None-Match&rdquo; do cache à requisição antes de bater na app Rails. A app Rails agora compara o digest &ldquo;If-None-Match&rdquo; recebido através do método &ldquo;stale?&rdquo; com o ETAG que acabou de computar e como eles batem, ela apenas manda uma resposta com body vazio e status code 304. O Rack Cache recebe o 304 e busca o JSON cacheado do Memcached e altera a resposta HTTP de 304 para um 200 normal com o body completo, que é o que o Curl pode receber.</p>
<p>Como estamos pulando a renderização cara da ActionView, o tempo de resposta caiu dos 26ms anteriores para cerca de 12ms: estamos agora <strong>duas vezes</strong> mais rápidos!</p>
<h3>Consumindo APIs com ETAGs<span class="hx:absolute hx:-mt-20" id="consumindo-apis-com-etags"></span>
    <a href="#consumindo-apis-com-etags" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Mas podemos ir um passo além. Se não mudarmos nada na 2a aplicação, ela vai continuar recebendo apenas HTTP 200 com bodies completos do Rack Cache da 1a aplicação. Vamos ver o código novamente</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># 2a aplicação</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Api</span><span class="o">::</span><span class="no">ProductsController</span> <span class="o">&lt;</span> <span class="no">ApplicationController</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">index</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># nunca, jamais, faça hardcode de hostnames assim, use dotenv-rails ou secrets.yml</span>
</span></span><span class="line"><span class="cl">    <span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;http://localhost:3001/api/products?page=?&#34;</span> <span class="o">%</span> <span class="p">(</span><span class="n">params</span><span class="o">[</span><span class="ss">:page</span><span class="o">]</span> <span class="o">||</span> <span class="s2">&#34;1&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">json</span> <span class="o">=</span> <span class="no">Net</span><span class="o">::</span><span class="no">HTTP</span><span class="o">.</span><span class="n">get_response</span><span class="p">(</span><span class="no">URI</span><span class="p">(</span><span class="n">url</span><span class="p">))</span><span class="o">.</span><span class="n">body</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">response</span><span class="o">.</span><span class="n">headers</span><span class="o">[</span><span class="s2">&#34;Content-Type&#34;</span><span class="o">]</span> <span class="o">=</span> <span class="s2">&#34;application/json&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">render</span> <span class="ss">plain</span><span class="p">:</span> <span class="n">json</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Podemos fazer melhor. Que tal o seguinte:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># 2a aplicação - upgrade!</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Api</span><span class="o">::</span><span class="no">ProductsController</span> <span class="o">&lt;</span> <span class="no">ApplicationController</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">index</span>
</span></span><span class="line"><span class="cl">    <span class="n">page</span> <span class="o">=</span> <span class="n">params</span><span class="o">[</span><span class="ss">:page</span><span class="o">]</span> <span class="o">||</span> <span class="s2">&#34;1&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;http://localhost:3001/api/products?page=?&#34;</span> <span class="o">%</span> <span class="n">page</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># 1 - busca o ETAG para a URL</span>
</span></span><span class="line"><span class="cl">    <span class="n">etag</span> <span class="o">=</span> <span class="no">Rails</span><span class="o">.</span><span class="n">cache</span><span class="o">.</span><span class="n">fetch</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># 2 - busca da API externa ou do cache interno</span>
</span></span><span class="line"><span class="cl">    <span class="n">json</span> <span class="o">=</span> <span class="n">fetch_with_etag</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">etag</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">response</span><span class="o">.</span><span class="n">headers</span><span class="o">[</span><span class="s2">&#34;Content-Type&#34;</span><span class="o">]</span> <span class="o">=</span> <span class="s2">&#34;application/json&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">render</span> <span class="ss">plain</span><span class="p">:</span> <span class="n">json</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="kp">private</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">fetch_with_etag</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">etag</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">uri</span> <span class="o">=</span> <span class="no">URI</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">req</span> <span class="o">=</span> <span class="no">Net</span><span class="o">::</span><span class="no">HTTP</span><span class="o">::</span><span class="no">Get</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">uri</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># 3 - adiciona o header importante If-None-Match</span>
</span></span><span class="line"><span class="cl">    <span class="n">req</span><span class="o">[</span><span class="s1">&#39;If-None-Match&#39;</span><span class="o">]</span> <span class="o">=</span> <span class="n">etag</span> <span class="k">if</span> <span class="n">etag</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">res</span> <span class="o">=</span> <span class="no">Net</span><span class="o">::</span><span class="no">HTTP</span><span class="o">.</span><span class="n">start</span><span class="p">(</span><span class="n">uri</span><span class="o">.</span><span class="n">hostname</span><span class="p">,</span> <span class="n">uri</span><span class="o">.</span><span class="n">port</span><span class="p">)</span> <span class="p">{</span><span class="o">|</span><span class="n">http</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">      <span class="n">http</span><span class="o">.</span><span class="n">request</span><span class="p">(</span><span class="n">req</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">etag</span> <span class="o">=</span> <span class="n">res</span><span class="o">[</span><span class="s1">&#39;ETAG&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">etag</span> <span class="o">=</span> <span class="n">etag</span><span class="o">[</span><span class="mi">1</span><span class="o">..-</span><span class="mi">2</span><span class="o">]</span> <span class="k">if</span> <span class="n">etag</span><span class="o">.</span><span class="n">present?</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">res</span><span class="o">.</span><span class="n">is_a?</span><span class="p">(</span><span class="no">Net</span><span class="o">::</span><span class="no">HTTPNotModified</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="c1"># 4 - se recebeu 304, então já temos o conteúdo no cache interno</span>
</span></span><span class="line"><span class="cl">      <span class="no">Rails</span><span class="o">.</span><span class="n">cache</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="n">etag</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">elsif</span> <span class="n">res</span><span class="o">.</span><span class="n">is_a?</span><span class="p">(</span><span class="no">Net</span><span class="o">::</span><span class="no">HTTPSuccess</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="c1"># 5 - se recebeu 200 é conteúdo novo para guardar no cache interno antes de retornar</span>
</span></span><span class="line"><span class="cl">      <span class="no">Rails</span><span class="o">.</span><span class="n">cache</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">etag</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="no">Rails</span><span class="o">.</span><span class="n">cache</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">etag</span><span class="p">,</span> <span class="n">res</span><span class="o">.</span><span class="n">body</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="n">res</span><span class="o">.</span><span class="n">body</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;{}&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Eu sei, parece exagerado, mas é bem simples na verdade. Vamos passo a passo:</p>
<ol>
<li>
<p>Primeiro vemos se já temos um ETAG associado à URL que queremos buscar (fique atento aos query parameters!)</p>
</li>
<li>
<p>Agora chamamos o método separado &ldquo;fetch_with_etag&rdquo;</p>
</li>
<li>
<p>Isso é todo o boilerplate de setup do &ldquo;Net::HTTP&rdquo;, mas a parte importante é que adicionamos o header &ldquo;If-None-Match&rdquo; se encontramos um ETAG para a URL no cache.</p>
</li>
<li>
<p>Depois de fazer a chamada externa podemos ter 2 respostas. A primeira é o &ldquo;304 Not Modified&rdquo; bem curtinho, sem body, só header. Neste caso, significa que já temos o conteúdo completo no cache interno e ele ainda é válido, então usamos ele.</p>
</li>
<li>
<p>No caso de recebermos o status code HTTP &ldquo;200&rdquo; normal, ou não mandamos nenhum ETAG ou aquele que mandamos foi invalidado e um novo ETAG e body de conteúdo foi retornado, então precisamos atualizar eles no nosso cache interno antes de sair.</p>
</li>
</ol>
<p>Agora, a primeira vez que chamamos &ldquo;curl http://localhost:3000/api/products?page=1&rdquo; para o endpoint da 2a aplicação vamos ver este log:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Started GET &#34;/api/products?page=1&#34; for 127.0.0.1 at 2016-03-23 14:14:05 -0300
Processing by Api::ProductsController#index as */*
  Parameters: {&#34;page&#34;=&gt;&#34;1&#34;}
  Rendered text template (0.0ms)
Completed 200 OK in 62ms (Views: 5.6ms | ActiveRecord: 0.0ms)</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Caches estão frios, está levando os mesmos &ldquo;cerca de 50ms&rdquo; como tínhamos antes, neste caso, mais para 62ms.</p>
<p>Só para recapitular, esta chamada para a 2a aplicação fez ela chamar a API da 1a aplicação, que mostra o seguinte no seu log:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Started GET &#34;/api/products?page=?&#34; for 127.0.0.1 at 2016-03-23 14:14:05 -0300
Processing by Api::ProductsController#index as */*
  Parameters: {&#34;page&#34;=&gt;&#34;?&#34;}
/&#34;32b82ebbd99854ea2ca0d49ff4a7c07c
  Product Load (0.9ms)  SELECT  &#34;products&#34;.* FROM &#34;products&#34; LIMIT 100 OFFSET 0
  Couldn&#39;t find template for digesting: products/index
   (1.2ms)  SELECT COUNT(*) FROM &#34;products&#34;
Completed 200 OK in 37ms (Views: 21.5ms | ActiveRecord: 2.2ms)
cache: [GET /api/products?page=?] miss, store</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Cache miss, novo conteúdo armazenado!</p>
<p>Agora, chamamos &ldquo;curl&rdquo; contra a mesma URL para a 2a aplicação novamente e agora vemos o que queríamos no log:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Started GET &#34;/api/products?page=1&#34; for 127.0.0.1 at 2016-03-23 14:14:10 -0300
Processing by Api::ProductsController#index as */*
  Parameters: {&#34;page&#34;=&gt;&#34;1&#34;}
  Rendered text template (0.0ms)
Completed 200 OK in 24ms (Views: 0.3ms | ActiveRecord: 0.0ms)</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>De 62ms para 24ms!! E no log da 1a aplicação vemos:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Started GET &#34;/api/products?page=?&#34; for 127.0.0.1 at 2016-03-23 14:14:10 -0300
Processing by Api::ProductsController#index as */*
  Parameters: {&#34;page&#34;=&gt;&#34;?&#34;}
&#34;ccf372c24cd259d0943fa3dc99830b10&#34;, ccf372c24cd259d0943fa3dc99830b10
  Product Load (1.2ms)  SELECT  &#34;products&#34;.* FROM &#34;products&#34; LIMIT 100 OFFSET 0
  Couldn&#39;t find template for digesting: products/index
Completed 304 Not Modified in 12ms (ActiveRecord: 1.2ms)
cache: [GET /api/products?page=?] stale, valid, store</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Cache hit! Conteúdo está stale e válido, então retorna apenas 304, a 2a aplicação confirma e busca o conteúdo ainda válido do seu próprio cache e devolve para o Curl.</p>
<h3>Conclusão<span class="hx:absolute hx:-mt-20" id="conclusão"></span>
    <a href="#conclus%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Se você remover ETAGs da 1a aplicação, a 2a não vai quebrar e vice-versa, porque é opcional. Se headers &ldquo;ETAG&rdquo; e &ldquo;If-None-Match&rdquo; estão presentes na resposta HTTP recebida, podemos usar eles, caso contrário vão funcionar como antes.</p>
<p>Se a 2a aplicação é ela mesma outra API você deveria adicionar ETAGs para ela também, e assim por diante. Neste exemplo não fizemos, apenas porque eu queria simplificar o cenário. Mas ao invés de ser apenas um simples proxy um-para-um, ela poderia ser uma daquelas APIs &ldquo;porcelain&rdquo; que buscam dados de vários outros microservices menores, compilam num único estrutura e retornam. Você deveria criar ETAGs que poderiam ser os ETAGs retornados de todos os outros microservices digeridos juntos num único ETAG, por exemplo. Como você está apenas recebendo headers e buscando conteúdo de um cache interno, é bem barato. Algo como este pseudo-código:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">index</span>
</span></span><span class="line"><span class="cl">  <span class="n">url1</span> <span class="o">=</span> <span class="s2">&#34;http://somehost1.foo/some_endpoint/1&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="n">url2</span> <span class="o">=</span> <span class="s2">&#34;http://somehost1.foo/some_endpoint/2&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="n">etag1</span> <span class="o">=</span> <span class="n">etag_from</span><span class="p">(</span><span class="n">url1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">etag2</span> <span class="o">=</span> <span class="n">etag_from</span><span class="p">(</span><span class="n">url2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">etag</span> <span class="o">=</span> <span class="no">Digest</span><span class="o">::</span><span class="no">MD5</span><span class="o">.</span><span class="n">hexdigest</span><span class="p">(</span><span class="n">etag1</span> <span class="o">+</span> <span class="n">etag2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="n">stale?</span><span class="p">(</span><span class="ss">etag</span><span class="p">:</span> <span class="n">etag</span><span class="p">,</span> <span class="kp">public</span><span class="p">:</span> <span class="kp">true</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">body1</span> <span class="o">=</span> <span class="no">JSON</span><span class="o">.</span><span class="n">parse</span> <span class="n">fetch_from</span><span class="p">(</span><span class="n">url1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">body2</span> <span class="o">=</span> <span class="no">JSON</span><span class="o">.</span><span class="n">parse</span> <span class="n">fetch_from</span><span class="p">(</span><span class="n">url2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">do_some_processing</span><span class="p">(</span><span class="n">body1</span><span class="p">,</span> <span class="n">body2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">render</span> <span class="ss">json</span><span class="p">:</span> <span class="n">result</span><span class="o">.</span><span class="n">to_json</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Outra coisa: você pode adicionar qualquer HTTP Cache vanilla entre seus microservices, para adicionar autorização, segurança, ou apenas cache extra simples, é só HTTP com headers adequados. Mas quanto mais &ldquo;304&rdquo; você troca entre seus serviços, menos processamento e menos banda você está gastando. Deve ser notavelmente eficiente na maioria dos casos. Mas de novo, nem sempre é barato ou trivial gerar as cache keys/ETAGs para começar, então este é o ponto onde precisa ter mais cuidado.</p>
<p>E se você está criando apps Javascript pesados que também consomem essas APIs, eu &ldquo;acredito&rdquo; que as chamadas Ajax cacheiam adequadamente o conteúdo HTTP e mandam de volta o &ldquo;If-None-Match&rdquo; correto e em caso de receber 304s, sua aplicação deve receber os triggers normais de &ldquo;success&rdquo;. Eu não testei isso quando estava escrevendo este post mas eu acho que é o caso de fato. Então você deveria automaticamente ter melhor performance na sua aplicação front-end de graça se adicionar ETAGs adequados nas suas APIs.</p>
<p>Isso é particularmente útil para APIs que retornam dados que mudam pouco. Se muda a cada segundo, ou a cada minuto, você não vai ver muito ganho. Mas se for algo como este exemplo: listas de produtos que mudam apenas uma vez por dia ou por semana, ou listas de CEP, ou Pedidos Anteriores num e-commerce. Qualquer dado que muda raramente é um bom candidato. E quanto maior o dataset, maiores os benefícios que você vai ver (se for uma listagem de um megabyte, por exemplo). Como sempre, isso também não é Bala de Prata, mas neste caso dá pouco trabalho adicionar ETAGs e tem efeitos colaterais quase nulos, então por que não?</p>
<p>ETAG é só uma das várias outras features HTTP que você deveria estar usando, CORS é outra (Pesquise <a href="https://github.com/cyu/rack-cors"target="_blank" rel="noopener">Rack Cors</a>).</p>
<p>Se você é do Brasil, deveria assistir o curso completo do Nando Vieira sobre o tema amplo de <a href="http://howtocode.com.br/cursos/rails-caching"target="_blank" rel="noopener">Rails Caching</a>.</p>
<p>Para ser honesto, eu não tenho certeza de quão efetiva esta técnica realmente pode ser em todos os tipos de cenários, então estou bem interessado em ouvir seu feedback caso você use algo assim nas suas aplicações.</p>
]]></content:encoded><category>performance</category><category>rails</category><category>sistemas-distribuidos</category></item><item><title>Consertando o Demo de Chat do DHH no Rails 5</title><link>https://akitaonrails.github.io/2015/12/28/consertando-o-demo-de-chat-do-dhh-no-rails-5/</link><guid isPermaLink="true">https://akitaonrails.github.io/2015/12/28/consertando-o-demo-de-chat-do-dhh-no-rails-5/</guid><pubDate>Mon, 28 Dec 2015 16:11:00 -0200</pubDate><description>&lt;p&gt;Pois é, o Rails 5.0.0 Beta 1 &lt;a href="http://weblog.rubyonrails.org/2015/12/18/Rails-5-0-beta1/"target="_blank" rel="noopener"&gt;acabou de ser lançado&lt;/a&gt; e a grande novidade é o &lt;a href="https://github.com/rails/rails/tree/master/actioncable"target="_blank" rel="noopener"&gt;Action Cable&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Basicamente é uma solução completa em cima do Rails padrão para você implementar aplicações baseadas em WebSocket (os famosos chats e notificações em tempo real) com acesso total ao que o Rails te dá (models, view templates, etc). Para apps pequenos e médios, é uma solução excelente que você pode usar no lugar de ter que partir para Node.js.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Pois é, o Rails 5.0.0 Beta 1 <a href="http://weblog.rubyonrails.org/2015/12/18/Rails-5-0-beta1/"target="_blank" rel="noopener">acabou de ser lançado</a> e a grande novidade é o <a href="https://github.com/rails/rails/tree/master/actioncable"target="_blank" rel="noopener">Action Cable</a>.</p>
<p>Basicamente é uma solução completa em cima do Rails padrão para você implementar aplicações baseadas em WebSocket (os famosos chats e notificações em tempo real) com acesso total ao que o Rails te dá (models, view templates, etc). Para apps pequenos e médios, é uma solução excelente que você pode usar no lugar de ter que partir para Node.js.</p>
<p>Resumindo, você controla Cable Channels que recebem mensagens enviadas por uma conexão WebSocket no cliente. O novo gerador de Channel cuida do boilerplate e você só precisa preencher os espaços em branco para definir que tipo de mensagem quer enviar do cliente, o que quer transmitir do servidor, e para quais channels seus clientes estão inscritos.</p>
<p>Para uma introdução mais detalhada, o próprio DHH publicou um <a href="https://www.youtube.com/watch?v=n0WUjGkDFS0"target="_blank" rel="noopener">screencast bem básico de Action Cable</a> que você deveria assistir só para ter uma ideia do porquê de toda essa empolgação. Se você já assistiu e tem alguma experiência em programação, talvez já tenha percebido o problema que menciono no título, então pula direto para a seção <a href="#the-problem">&ldquo;O Problema&rdquo;</a> abaixo para o TL;DR.</p>
<p>No final você acaba com uma base de código como a que reproduzi no meu repositório do Github até a <a href="https://github.com/akitaonrails/rails5-actioncable-demo/tree/end_of_dhh"target="_blank" rel="noopener">tag &ldquo;end_of_dhh&rdquo;</a>. Você vai ter um chat em tempo real (bem) básico de uma única sala para brincar com os principais componentes.</p>
<p>Vamos só listar os principais componentes aqui. Primeiro, você terá o servidor ActionCable montado no arquivo &ldquo;routes.rb&rdquo;:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># config/routes.rb</span>
</span></span><span class="line"><span class="cl"><span class="no">Rails</span><span class="o">.</span><span class="n">application</span><span class="o">.</span><span class="n">routes</span><span class="o">.</span><span class="n">draw</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">root</span> <span class="ss">to</span><span class="p">:</span> <span class="s1">&#39;rooms#show&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Serve websocket cable requests in-process</span>
</span></span><span class="line"><span class="cl">  <span class="n">mount</span> <span class="no">ActionCable</span><span class="o">.</span><span class="n">server</span> <span class="o">=&gt;</span> <span class="s1">&#39;/cable&#39;</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Este é o componente principal do servidor, o channel:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># app/channels/room_channel.rb</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">RoomChannel</span> <span class="o">&lt;</span> <span class="no">ApplicationCable</span><span class="o">::</span><span class="no">Channel</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">subscribed</span>
</span></span><span class="line"><span class="cl">    <span class="n">stream_from</span> <span class="s2">&#34;room_channel&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">unsubscribed</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Any cleanup needed when channel is unsubscribed</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">speak</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="no">Message</span><span class="o">.</span><span class="n">create!</span> <span class="ss">content</span><span class="p">:</span> <span class="n">data</span><span class="o">[</span><span class="s1">&#39;message&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Depois você tem o Javascript boilerplate:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># app/assets/javascripts/cable.coffee</span>
</span></span><span class="line"><span class="cl"><span class="c1">#= require action_cable</span>
</span></span><span class="line"><span class="cl"><span class="c1">#= require_self</span>
</span></span><span class="line"><span class="cl"><span class="c1">#= require_tree ./channels</span>
</span></span><span class="line"><span class="cl"><span class="c1">#</span>
</span></span><span class="line"><span class="cl"><span class="vi">@App</span> <span class="o">||=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl"><span class="no">App</span><span class="o">.</span><span class="n">cable</span> <span class="o">=</span> <span class="no">ActionCable</span><span class="o">.</span><span class="n">createConsumer</span><span class="p">()</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E os principais hooks de Websocket no lado do cliente:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># app/assets/javascripts/channels/room.coffee</span>
</span></span><span class="line"><span class="cl"><span class="no">App</span><span class="o">.</span><span class="n">room</span> <span class="o">=</span> <span class="no">App</span><span class="o">.</span><span class="n">cable</span><span class="o">.</span><span class="n">subscriptions</span><span class="o">.</span><span class="n">create</span> <span class="s2">&#34;RoomChannel&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="ss">connected</span><span class="p">:</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Called when the subscription is ready for use on the server</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="ss">disconnected</span><span class="p">:</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Called when the subscription has been terminated by the server</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="ss">received</span><span class="p">:</span> <span class="p">(</span><span class="n">data</span><span class="p">)</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="err">$</span><span class="p">(</span><span class="s1">&#39;#messages&#39;</span><span class="p">)</span><span class="o">.</span><span class="n">append</span> <span class="n">data</span><span class="o">[</span><span class="s1">&#39;message&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="ss">speak</span><span class="p">:</span> <span class="p">(</span><span class="n">message</span><span class="p">)</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="vi">@perform</span> <span class="s1">&#39;speak&#39;</span><span class="p">,</span> <span class="ss">message</span><span class="p">:</span> <span class="n">message</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="err">$</span><span class="p">(</span><span class="n">document</span><span class="p">)</span><span class="o">.</span><span class="n">on</span> <span class="s2">&#34;keypress&#34;</span><span class="p">,</span> <span class="s2">&#34;[data-behavior~=room_speaker]&#34;</span><span class="p">,</span> <span class="p">(</span><span class="n">event</span><span class="p">)</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="n">event</span><span class="o">.</span><span class="n">keyCode</span> <span class="n">is</span> <span class="mi">13</span>
</span></span><span class="line"><span class="cl">    <span class="no">App</span><span class="o">.</span><span class="n">room</span><span class="o">.</span><span class="n">speak</span> <span class="n">event</span><span class="o">.</span><span class="n">target</span><span class="o">.</span><span class="n">value</span>
</span></span><span class="line"><span class="cl">    <span class="n">event</span><span class="o">.</span><span class="n">target</span><span class="o">.</span><span class="n">value</span> <span class="o">=</span> <span class="s1">&#39;&#39;</span>
</span></span><span class="line"><span class="cl">    <span class="n">event</span><span class="o">.</span><span class="n">preventDefault</span><span class="p">()</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O view template é um HTML básico só para amarrar um formulário simples e uma div para listar as mensagens:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="c">&lt;!-- app/views/rooms/show.html.erb --&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">h1</span><span class="p">&gt;</span>Chat room<span class="p">&lt;/</span><span class="nt">h1</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">div</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;messages&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="err">&lt;</span>%= render @messages %&gt;
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">form</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">label</span><span class="p">&gt;</span>Say something:<span class="p">&lt;/</span><span class="nt">label</span><span class="p">&gt;&lt;</span><span class="nt">br</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;text&#34;</span> <span class="na">data-behavior</span><span class="o">=</span><span class="s">&#34;room_speaker&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">form</span><span class="p">&gt;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p><a name="the-problem"></a></p>
<h3>O Problema<span class="hx:absolute hx:-mt-20" id="o-problema"></span>
    <a href="#o-problema" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>No &ldquo;RoomChannel&rdquo;, você tem o método &ldquo;<tt>speak</tt>&rdquo; que salva uma mensagem no banco de dados. Isso já é um sinal vermelho para uma ação de WebSocket, que deveria ter processamento curto e leve. Salvar no banco é considerado pesado, principalmente sob carga. Se isso for processado dentro do reactor loop do EventMachine, vai travar o loop e impedir que outro processamento concorrente aconteça até o banco soltar o lock.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># app/channels/room_channel.rb</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">RoomChannel</span> <span class="o">&lt;</span> <span class="no">ApplicationCable</span><span class="o">::</span><span class="no">Channel</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">speak</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="no">Message</span><span class="o">.</span><span class="n">create!</span> <span class="ss">content</span><span class="p">:</span> <span class="n">data</span><span class="o">[</span><span class="s1">&#39;message&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Eu diria que tudo que entra dentro do channel deveria ser assíncrono!</p>
<p>Para piorar a situação, é isso que você tem dentro do próprio model &ldquo;Message&rdquo;:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Message</span> <span class="o">&lt;</span> <span class="no">ApplicationRecord</span>
</span></span><span class="line"><span class="cl">  <span class="n">after_create_commit</span> <span class="p">{</span> <span class="no">MessageBroadcastJob</span><span class="o">.</span><span class="n">perform_later</span> <span class="nb">self</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Um callback de model (fuja desses como da peste!!) para transmitir a mensagem recebida para os clientes Websocket inscritos como um ActiveJob, que se parece com isso:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">MessageBroadcastJob</span> <span class="o">&lt;</span> <span class="no">ApplicationJob</span>
</span></span><span class="line"><span class="cl">  <span class="n">queue_as</span> <span class="ss">:default</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">perform</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="no">ActionCable</span><span class="o">.</span><span class="n">server</span><span class="o">.</span><span class="n">broadcast</span>  <span class="s1">&#39;room_channel&#39;</span><span class="p">,</span> <span class="ss">message</span><span class="p">:</span> <span class="n">render_message</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kp">private</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">render_message</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="no">ApplicationController</span><span class="o">.</span><span class="n">renderer</span><span class="o">.</span><span class="n">render</span><span class="p">(</span><span class="ss">partial</span><span class="p">:</span> <span class="s1">&#39;messages/message&#39;</span><span class="p">,</span> <span class="ss">locals</span><span class="p">:</span> <span class="p">{</span> <span class="ss">message</span><span class="p">:</span> <span class="n">message</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Ele renderiza o trecho de HTML para mandar de volta para os clientes Websocket adicionarem ao DOM dos seus navegadores.</p>
<p>O DHH ainda chega a dizer <em>&ldquo;Eu queria mostrar isso porque é assim que a maioria dos apps vai acabar ficando.&rdquo;</em></p>
<p>E de fato, o <strong>problema</strong> é justamente que a maioria das pessoas vai seguir esse padrão e cair nessa armadilha. Então, qual a solução então?</p>
<h3>A Solução Correta<span class="hx:absolute hx:-mt-20" id="a-solução-correta"></span>
    <a href="#a-solu%c3%a7%c3%a3o-correta" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Só para os fins de um screencast simples, vamos fazer um conserto rápido.</p>
<p>Antes de tudo, se for possível, você quer que o código do seu channel bloqueie o mínimo possível. Esperar uma operação bloqueante no banco (gravação) definitivamente não é uma dessas operações. O Job está sendo subutilizado, ele deveria ser chamado direto do método &ldquo;speak&rdquo; do channel, assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># app/channels/room_channel.rb</span>
</span></span><span class="line"><span class="cl"> <span class="k">class</span> <span class="nc">RoomChannel</span> <span class="o">&lt;</span> <span class="no">ApplicationCable</span><span class="o">::</span><span class="no">Channel</span>
</span></span><span class="line"><span class="cl">   <span class="o">...</span>
</span></span><span class="line"><span class="cl">   <span class="k">def</span> <span class="nf">speak</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="o">-</span>    <span class="no">Message</span><span class="o">.</span><span class="n">create!</span> <span class="ss">content</span><span class="p">:</span> <span class="n">data</span><span class="o">[</span><span class="s1">&#39;message&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="o">+</span>    <span class="no">MessageBroadcastJob</span><span class="o">.</span><span class="n">perform_later</span> <span class="n">data</span><span class="o">[</span><span class="s1">&#39;message&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">   <span class="k">end</span>
</span></span><span class="line"><span class="cl"> <span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Depois, movemos a gravação no model para dentro do próprio Job:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># app/jobs/message_broadcast_job.rb</span>
</span></span><span class="line"><span class="cl"> <span class="k">class</span> <span class="nc">MessageBroadcastJob</span> <span class="o">&lt;</span> <span class="no">ApplicationJob</span>
</span></span><span class="line"><span class="cl">   <span class="n">queue_as</span> <span class="ss">:default</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">-</span>  <span class="k">def</span> <span class="nf">perform</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="o">-</span>    <span class="no">ActionCable</span><span class="o">.</span><span class="n">server</span><span class="o">.</span><span class="n">broadcast</span>  <span class="s1">&#39;room_channel&#39;</span><span class="p">,</span> <span class="ss">message</span><span class="p">:</span> <span class="n">render_message</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="o">+</span>  <span class="k">def</span> <span class="nf">perform</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="o">+</span>    <span class="n">message</span> <span class="o">=</span> <span class="no">Message</span><span class="o">.</span><span class="n">create!</span> <span class="ss">content</span><span class="p">:</span> <span class="n">data</span>
</span></span><span class="line"><span class="cl"><span class="o">+</span>    <span class="no">ActionCable</span><span class="o">.</span><span class="n">server</span><span class="o">.</span><span class="n">broadcast</span> <span class="s1">&#39;room_channel&#39;</span><span class="p">,</span> <span class="ss">message</span><span class="p">:</span> <span class="n">render_message</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">   <span class="k">end</span>
</span></span><span class="line"><span class="cl">   <span class="o">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E finalmente, removemos aquele callback horrível do model e deixamos ele limpo de novo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># app/models/message.rb</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Message</span> <span class="o">&lt;</span> <span class="no">ApplicationRecord</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Isso retorna rápido, joga o processamento para um job em background e deveria suportar mais concorrência de saída. A solução anterior, do DHH, tem um gargalo embutido no método speak e vai engasgar assim que o banco virar o gargalo.</p>
<p>Está longe de ser uma solução perfeita ainda, mas é menos terrível para um demo bem rápido e o código fica até mais simples. Você pode conferir esse código no <a href="https://github.com/akitaonrails/rails5-actioncable-demo/commit/0aaaaecc46ed14e98086bac5ce087df08d557456"target="_blank" rel="noopener">meu commit no Github</a>.</p>
<p>Posso estar errado na conclusão de que o channel vai bloquear ou se isso é mesmo prejudicial para a concorrência. Não medi as duas soluções, é só uma intuição vinda de feridas antigas. Se você tem mais conhecimento sobre a implementação do Action Cable, deixa um comentário aí embaixo.</p>
<p>Aliás, tenha cuidado antes de pensar em migrar seu app de Rails 4.2 para Rails 5 já. Por causa das dependências hard coded em Faye e Eventmachine, o Rails 5 hoje exclui o Unicorn (até o Thin parece estar tendo problema para subir). Também exclui JRuby e MRI no Windows por causa do Eventmachine.</p>
<p>Se você quer as capacidades do Action Cable sem precisar migrar, pode usar soluções como o <a href="http://developers.planningcenteronline.com/2014/09/23/live-updating-rails-with-react.js-and-pusher.html"target="_blank" rel="noopener">&ldquo;Pusher.com&rdquo;</a>, ou se quer sua própria solução in-house, acompanhe minha evolução no assunto com o meu <a href="http://www.akitaonrails.com/pusher"target="_blank" rel="noopener">mini clone do Pusher</a> escrito em Elixir.</p>
]]></content:encoded><category>rails</category><category>backend</category><category>sistemas-distribuidos</category></item><item><title>Ex Pusher Lite - Parte 2 - O Primeiro Core Funcional!</title><link>https://akitaonrails.github.io/2015/12/14/ex-pusher-lite-parte-2-o-primeiro-core-funcional/</link><guid isPermaLink="true">https://akitaonrails.github.io/2015/12/14/ex-pusher-lite-parte-2-o-primeiro-core-funcional/</guid><pubDate>Mon, 14 Dec 2015 15:04:00 -0200</pubDate><description>&lt;p&gt;Na &lt;a href="http://www.akitaonrails.com/2015/12/09/ex-pusher-lite-parte-1-phoenix-channels-e-aplicacoes-rails"target="_blank" rel="noopener"&gt;Parte 1&lt;/a&gt; eu basicamente comecei a partir do tutorial do &lt;a href="https://labs.opendoor.com/phoenix-on-rails-for-client-push-notifications"target="_blank" rel="noopener"&gt;Daniel Neighman&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Nesta Parte 2 vou adicionar os mecanismos necessários para ter um core mínimo que seja realmente útil e fazer deploy no Heroku. Para isso preciso implementar o seguinte:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;uma autenticação simples de Administração (um admin_username e admin_password hardcoded já resolvem por enquanto)&lt;/li&gt;
&lt;li&gt;um endpoint &lt;strong&gt;&amp;quot;/api/admin/apps&amp;quot;&lt;/strong&gt; restrito ao admin para gerenciar novas Aplicações. Cada Aplicação deve ter uma key e um secret gerados aleatoriamente.&lt;/li&gt;
&lt;li&gt;o endpoint &amp;ldquo;/events&amp;rdquo; da Parte 1 deve ser movido para &lt;strong&gt;&amp;quot;/api/apps/:app_id/events&amp;quot;&lt;/strong&gt; e ter o acesso restrito pela autenticação da key e secret da Aplicação identificada como &amp;ldquo;app_id&amp;rdquo;.&lt;/li&gt;
&lt;li&gt;por enquanto, o Event vai apenas fazer broadcast no tópico apropriado. Queremos poder fazer broadcast pra todo mundo de uma Aplicação e também pra tópicos específicos dentro do escopo daquela Aplicação.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Como de costume, o código desta seção vai estar marcado com a tag &lt;strong&gt;v0.2&lt;/strong&gt; tanto no repositório do &lt;a href="https://github.com/akitaonrails/pusher_lite_demo/tree/v0.2"target="_blank" rel="noopener"&gt;client demo&lt;/a&gt; quanto no &lt;a href="https://github.com/akitaonrails/ex_pusher_lite/tree/v0.2"target="_blank" rel="noopener"&gt;server-side&lt;/a&gt; no Github.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Na <a href="http://www.akitaonrails.com/2015/12/09/ex-pusher-lite-parte-1-phoenix-channels-e-aplicacoes-rails"target="_blank" rel="noopener">Parte 1</a> eu basicamente comecei a partir do tutorial do <a href="https://labs.opendoor.com/phoenix-on-rails-for-client-push-notifications"target="_blank" rel="noopener">Daniel Neighman</a>.</p>
<p>Nesta Parte 2 vou adicionar os mecanismos necessários para ter um core mínimo que seja realmente útil e fazer deploy no Heroku. Para isso preciso implementar o seguinte:</p>
<ul>
<li>uma autenticação simples de Administração (um admin_username e admin_password hardcoded já resolvem por enquanto)</li>
<li>um endpoint <strong>&quot;/api/admin/apps&quot;</strong> restrito ao admin para gerenciar novas Aplicações. Cada Aplicação deve ter uma key e um secret gerados aleatoriamente.</li>
<li>o endpoint &ldquo;/events&rdquo; da Parte 1 deve ser movido para <strong>&quot;/api/apps/:app_id/events&quot;</strong> e ter o acesso restrito pela autenticação da key e secret da Aplicação identificada como &ldquo;app_id&rdquo;.</li>
<li>por enquanto, o Event vai apenas fazer broadcast no tópico apropriado. Queremos poder fazer broadcast pra todo mundo de uma Aplicação e também pra tópicos específicos dentro do escopo daquela Aplicação.</li>
</ul>
<p>Como de costume, o código desta seção vai estar marcado com a tag <strong>v0.2</strong> tanto no repositório do <a href="https://github.com/akitaonrails/pusher_lite_demo/tree/v0.2"target="_blank" rel="noopener">client demo</a> quanto no <a href="https://github.com/akitaonrails/ex_pusher_lite/tree/v0.2"target="_blank" rel="noopener">server-side</a> no Github.</p>
<h3>O Resource App<span class="hx:absolute hx:-mt-20" id="o-resource-app"></span>
    <a href="#o-resource-app" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Se este nosso projeto vai se comportar como o Pusher.com, precisamos de uma forma de criar novas &ldquo;Aplicações&rdquo;. Cada cliente que se conecta a esse serviço vai estar atrelado a essa Aplicação. Os Events ficam restritos à fronteira da Aplicação. É assim que vamos isolar os diferentes clientes que se conectam ao mesmo servidor. Então você pode ter um único core servindo várias aplicações web diferentes.</p>
<p>Quando uma nova aplicação é criada, o web app cliente/consumidor recebe o par de tokens key e secret que vai usar tanto para os triggers do lado servidor quanto para os listeners de Websocket no lado cliente.</p>
<p>Como aviso prévio, neste estágio do desenvolvimento eu não vou implementar nenhum sistema sofisticado de autenticação como OAuth2 ou JWT. Vou deixar isso pra posts futuros. Por enquanto vou usar a key e o secret da Aplicação como simples username e password num HTTP Basic Auth. Por ora isso já basta pros nossos propósitos.</p>
<p>Então, o primeiro passo é criar esse resource &ldquo;Application&rdquo; e podemos recorrer ao gerador de scaffold JSON que já vem com o Phoenix:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>mix phoenix.gen.json App apps name:string slug:string key:string secret:string active:boolean</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>A maioria dos tutoriais mostra o gerador &ldquo;<tt>phoenix.gen.html</tt>&rdquo;, que se comporta como o &ldquo;scaffold&rdquo; do Rails, gerando templates HTML pra cada um dos verbos CRUD. Esse aqui é parecido mas pula o HTML e assume que vai ser uma API CRUD em JSON.</p>
<p>Precisamos atualizar manualmente o arquivo &ldquo;<tt>web/router.ex</tt>&rdquo; assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># web/router.ex</span>
</span></span><span class="line"><span class="cl"><span class="n">scope</span> <span class="s2">&#34;/api&#34;</span><span class="p">,</span> <span class="no">ExPusherLite</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">pipe_through</span> <span class="ss">:api</span>
</span></span><span class="line"><span class="cl">  <span class="n">post</span> <span class="s2">&#34;/apps/:app_slug/events&#34;</span><span class="p">,</span> <span class="no">EventsController</span><span class="p">,</span> <span class="ss">:create</span>
</span></span><span class="line"><span class="cl">  <span class="n">scope</span> <span class="s2">&#34;/admin&#34;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">resources</span> <span class="s2">&#34;/apps&#34;</span><span class="p">,</span> <span class="no">AppController</span><span class="p">,</span> <span class="ss">except</span><span class="p">:</span> <span class="o">[</span><span class="ss">:new</span><span class="p">,</span> <span class="ss">:edit</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O &ldquo;<tt>EventsController</tt>&rdquo; é aquele que implementamos na Parte 1 e que vamos refazer durante esta Parte 2.</p>
<p>O gerador nos deu esse novo &ldquo;<tt>AppController</tt>&rdquo; e, assim como nas rotas do Rails, a DSL aqui é notavelmente parecida. Se você é um Railer aposto que reconhece de cara as rotas que essa DSL está gerando.</p>
<p>O gerador também criou uma migration apropriada pra gente:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># priv/repo/migrations/20151210131528_create_app.exs</span>
</span></span><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">ExPusherLite</span><span class="o">.</span><span class="n">Repo</span><span class="o">.</span><span class="n">Migrations</span><span class="o">.</span><span class="n">CreateApp</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">Ecto</span><span class="o">.</span><span class="n">Migration</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">change</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">create</span> <span class="n">table</span><span class="p">(</span><span class="ss">:apps</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="n">add</span> <span class="ss">:name</span><span class="p">,</span> <span class="ss">:string</span>
</span></span><span class="line"><span class="cl">      <span class="n">add</span> <span class="ss">:slug</span><span class="p">,</span> <span class="ss">:string</span>
</span></span><span class="line"><span class="cl">      <span class="n">add</span> <span class="ss">:key</span><span class="p">,</span> <span class="ss">:string</span>
</span></span><span class="line"><span class="cl">      <span class="n">add</span> <span class="ss">:secret</span><span class="p">,</span> <span class="ss">:string</span>
</span></span><span class="line"><span class="cl">      <span class="n">add</span> <span class="ss">:active</span><span class="p">,</span> <span class="ss">:boolean</span><span class="p">,</span> <span class="ss">default</span><span class="p">:</span> <span class="kp">false</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">timestamps</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">    <span class="n">create</span> <span class="n">index</span><span class="p">(</span><span class="ss">:apps</span><span class="p">,</span> <span class="o">[</span><span class="ss">:name</span><span class="o">]</span><span class="p">,</span> <span class="ss">unique</span><span class="p">:</span> <span class="kp">true</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">create</span> <span class="n">index</span><span class="p">(</span><span class="ss">:apps</span><span class="p">,</span> <span class="o">[</span><span class="ss">:slug</span><span class="o">]</span><span class="p">,</span> <span class="ss">unique</span><span class="p">:</span> <span class="kp">true</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>De novo, notavelmente parecido com a DSL de Migration do ActiveRecord. Migrations se comportam exatamente como você espera. Você precisa rodar:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>mix ecto.create # se ainda não tiver feito isso
mix ecto.migrate</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Este resource App vai precisar gerar slugs a partir dos nomes (que vamos usar como &ldquo;app_id&rdquo;) e também gerar valores aleatórios para key e secret. Então temos que adicionar essas dependências no arquivo &ldquo;<tt>mix.exs</tt>&rdquo;:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># mix.exs</span>
</span></span><span class="line"><span class="cl"><span class="n">defp</span> <span class="n">deps</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="o">[...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="p">{</span><span class="ss">:secure_random</span><span class="p">,</span> <span class="s2">&#34;~&gt; 0.2.0&#34;</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">   <span class="p">{</span><span class="ss">:slugger</span><span class="p">,</span> <span class="s2">&#34;~&gt; 0.0.1&#34;</span><span class="p">}</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O <a href="https://github.com/akitaonrails/ex_pusher_lite/blob/v0.2/web/models/app.ex"target="_blank" rel="noopener">model App final</a> é bem grande, então vou destrinchar pra vocês:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># web/models/app.ex</span>
</span></span><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">ExPusherLite</span><span class="o">.</span><span class="n">App</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">ExPusherLite</span><span class="o">.</span><span class="n">Web</span><span class="p">,</span> <span class="ss">:model</span>
</span></span><span class="line"><span class="cl">  <span class="k">alias</span> <span class="no">ExPusherLite</span><span class="o">.</span><span class="n">Repo</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">schema</span> <span class="s2">&#34;apps&#34;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">field</span> <span class="ss">:name</span><span class="p">,</span> <span class="ss">:string</span>
</span></span><span class="line"><span class="cl">    <span class="n">field</span> <span class="ss">:slug</span><span class="p">,</span> <span class="ss">:string</span>
</span></span><span class="line"><span class="cl">    <span class="n">field</span> <span class="ss">:key</span><span class="p">,</span> <span class="ss">:string</span>
</span></span><span class="line"><span class="cl">    <span class="n">field</span> <span class="ss">:secret</span><span class="p">,</span> <span class="ss">:string</span>
</span></span><span class="line"><span class="cl">    <span class="n">field</span> <span class="ss">:active</span><span class="p">,</span> <span class="ss">:boolean</span><span class="p">,</span> <span class="ss">default</span><span class="p">:</span> <span class="kp">true</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">timestamps</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="vi">@required_fields</span> <span class="o">~</span><span class="n">w</span><span class="p">(</span><span class="nb">name</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="vi">@optional_fields</span> <span class="o">~</span><span class="n">w</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Esse bloco declara o Schema do model. Cuidado quando você gera uma migration e depois muda as configurações dos campos: você precisa lembrar de atualizar o schema no model. Na minha primeira tentativa eu não incluí o campo &ldquo;slug&rdquo;, então fiz rollback da migration (&quot;<tt>mix ecto.rollback</tt>&quot;), mudei a migration pra adicionar o campo &ldquo;slug&rdquo; e rodei a task &ldquo;<tt>ecto.migrate</tt>&rdquo; de novo.</p>
<p>Fiquei intrigado porque o model não estava pegando o novo campo; depois de um tempo lembrei que models do Ecto não tentam buscar o schema real do banco e gerar accessors dinamicamente, mas dependem do bloco de schema declarado explicitamente como mostrado acima. Depois que adicionei o campo &ldquo;slug&rdquo; no bloco de schema, o model passou a usar o novo campo direitinho.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># web/models/app.ex</span>
</span></span><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">get_by_slug</span><span class="p">(</span><span class="n">slug</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="no">Repo</span><span class="o">.</span><span class="n">get_by!</span><span class="p">(</span><span class="n">__MODULE__</span><span class="p">,</span> <span class="ss">slug</span><span class="p">:</span> <span class="n">slug</span><span class="p">,</span> <span class="ss">active</span><span class="p">:</span> <span class="kp">true</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">hashed_secret</span><span class="p">(</span><span class="n">model</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="no">Base</span><span class="o">.</span><span class="n">encode64</span><span class="p">(</span><span class="s2">&#34;</span><span class="si">#{</span><span class="n">model</span><span class="o">.</span><span class="n">key</span><span class="si">}</span><span class="s2">:</span><span class="si">#{</span><span class="n">model</span><span class="o">.</span><span class="n">secret</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="o">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Essas são apenas funções helper pra usar no AppController. A parte estranha talvez seja &ldquo;<strong>MODULE</strong>&rdquo;, mas isso é só um atalho pra representação em atom do módulo atual, que é &ldquo;<tt>ExPusherLite.App</tt>&rdquo;. É assim que se faz uma query simples ao model, lembra muito o &ldquo;<tt>App.get_by(slug: slug, active: true)</tt>&rdquo; do Rails.</p>
<p>Por convenção do Elixir, o Ecto tem funções com e sem bang (&quot;<tt>get_by!</tt>&quot; e &ldquo;<tt>get_by</tt>&rdquo;). Se você quiser capturar um erro, usa a versão sem bang e ela retorna ou uma tupla &ldquo;<tt>{:ok, result}</tt>&rdquo; ou &ldquo;<tt>{:error, result}</tt>&rdquo; e você pode fazer pattern match. Ou usa a versão com bang e ela levanta uma exception. Depende do que você quer fazer.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># web/models/app.ex</span>
</span></span><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">changeset</span><span class="p">(</span><span class="n">model</span><span class="p">,</span> <span class="n">params</span> <span class="p">\\</span> <span class="ss">:empty</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">model</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="n">cast</span><span class="p">(</span><span class="n">params</span><span class="p">,</span> <span class="vi">@required_fields</span><span class="p">,</span> <span class="vi">@optional_fields</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="n">validate_length</span><span class="p">(</span><span class="ss">:name</span><span class="p">,</span> <span class="ss">min</span><span class="p">:</span> <span class="mi">5</span><span class="p">,</span> <span class="ss">max</span><span class="p">:</span> <span class="mi">255</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="n">unique_constraint</span><span class="p">(</span><span class="ss">:name</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="n">generate_key</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="n">generate_secret</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="n">slugify</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="n">unique_constraint</span><span class="p">(</span><span class="ss">:slug</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="o">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Atrás apenas do bloco de Schema que mencionei acima, esta função &ldquo;<tt>changeset/2</tt>&rdquo; é a parte mais importante de um Model.</p>
<p>No Rails você tem só o conceito de &ldquo;Model&rdquo;, que é considerado &ldquo;Fat&rdquo; porque cuida de operações de banco, lógica de negócio e hooks do framework tudo no mesmo lugar. No Phoenix você lida com pelo menos 3 conceitos diferentes:</p>
<ul>
<li>Você tem um <strong>Repo</strong>, que recebe um Changeset e usa pra inserir ou atualizar as linhas designadas na tabela do banco. Você vai ver &ldquo;<tt>Repo.get</tt>&rdquo; ou &ldquo;<tt>Repo.insert</tt>&rdquo;, e não &ldquo;<tt>App.find</tt>&rdquo; ou &ldquo;<tt>App.save</tt>&rdquo;.</li>
<li>Aí você tem o <strong>Changeset</strong>, que são apenas Maps do Elixir (aqueles com a sintaxe &ldquo;%<tt>{key =&gt; value}</tt>&rdquo;). É só um Hash, um Dictionary, uma coleção de pares chave-valor. Um Repo aceita Maps pras suas operações.</li>
<li>Por fim, você tem o <strong>Model</strong>, que de fato dá significado e contexto aos Changesets crus. E a função &ldquo;<tt>changeset/2</tt>&rdquo; acima é a responsável por receber um Map cru, passá-lo por uma cadeia de validação e transformação e devolver um Changeset válido pro Repo usar.</li>
</ul>
<p>Então, num controller você normalmente vai encontrar código assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># web/controllers/app_controller.ex</span>
</span></span><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">create</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="sx">%{&#34;app&#34; =&gt; app_params}</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">changeset</span> <span class="o">=</span> <span class="no">App</span><span class="o">.</span><span class="n">changeset</span><span class="p">(</span><span class="o">%</span><span class="no">App</span><span class="p">{},</span> <span class="n">app_params</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl">  <span class="k">case</span> <span class="no">Repo</span><span class="o">.</span><span class="n">insert</span><span class="p">(</span><span class="n">changeset</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">app</span><span class="p">}</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="n">conn</span>
</span></span><span class="line"><span class="cl">      <span class="o">|&gt;</span> <span class="o">...</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="ss">:error</span><span class="p">,</span> <span class="n">changeset</span><span class="p">}</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="n">conn</span>
</span></span><span class="line"><span class="cl">      <span class="o">|&gt;</span> <span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>É assim que você cria um changeset novo, validado, e passa pro Repo, tratando os resultados num bloco de pattern match. Compare a linha do changeset acima com o início da função &ldquo;changeset/2&rdquo;:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">changeset</span><span class="p">(</span><span class="n">model</span><span class="p">,</span> <span class="n">params</span> <span class="p">\\</span> <span class="ss">:empty</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">model</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="n">cast</span><span class="p">(</span><span class="n">params</span><span class="p">,</span> <span class="vi">@required_fields</span><span class="p">,</span> <span class="vi">@optional_fields</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="n">validate_length</span><span class="p">(</span><span class="ss">:name</span><span class="p">,</span> <span class="ss">min</span><span class="p">:</span> <span class="mi">5</span><span class="p">,</span> <span class="ss">max</span><span class="p">:</span> <span class="mi">255</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="o">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Ela mapeia o registro vazio &ldquo;<tt>%App{}</tt>&rdquo; pro argumento &ldquo;model&rdquo; e o &ldquo;app_params&rdquo; que vem do request (um map do formato &ldquo;<tt>%{name =&gt; &lsquo;foo&rsquo;, active: =&gt; &rsquo;true&rsquo;}</tt>&rdquo;) pro argumento &ldquo;params&rdquo;. Depois encanou o model e os params na função &ldquo;<tt>cast/4</tt>&rdquo;, que vai copiar os valores do map de params pro map/changeset do model. E vai passando o changeset resultante pras funções seguintes, como o &ldquo;<tt>validated_length/3</tt>&rdquo; abaixo, e assim por diante. Se a cadeia termina sem exceptions, você tem no final um changeset limpo, validado, que é só passar pro Repo enfiar no banco sem pensar.</p>
<p>Na implementação acima estamos encadeando filtros pra gerar key, secret e slug, e essa é a implementação como funções privadas:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># web/models/app.ex</span>
</span></span><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="n">defp</span> <span class="n">generate_key</span><span class="p">(</span><span class="n">model</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">get_field</span><span class="p">(</span><span class="n">model</span><span class="p">,</span> <span class="ss">:key</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="n">model</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span>
</span></span><span class="line"><span class="cl">      <span class="n">model</span>
</span></span><span class="line"><span class="cl">      <span class="o">|&gt;</span> <span class="n">put_change</span><span class="p">(</span><span class="ss">:key</span><span class="p">,</span> <span class="no">SecureRandom</span><span class="o">.</span><span class="n">uuid</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">defp</span> <span class="n">generate_secret</span><span class="p">(</span><span class="n">model</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">get_field</span><span class="p">(</span><span class="n">model</span><span class="p">,</span> <span class="ss">:secret</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="n">model</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span>
</span></span><span class="line"><span class="cl">      <span class="n">model</span>
</span></span><span class="line"><span class="cl">      <span class="o">|&gt;</span> <span class="n">put_change</span><span class="p">(</span><span class="ss">:secret</span><span class="p">,</span> <span class="no">SecureRandom</span><span class="o">.</span><span class="n">uuid</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">defp</span> <span class="n">slugify</span><span class="p">(</span><span class="n">model</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nb">name</span> <span class="o">=</span> <span class="n">get_change</span><span class="p">(</span><span class="n">model</span><span class="p">,</span> <span class="ss">:name</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="n">model</span>
</span></span><span class="line"><span class="cl">      <span class="o">|&gt;</span> <span class="n">put_change</span><span class="p">(</span><span class="ss">:slug</span><span class="p">,</span> <span class="no">Slugger</span><span class="o">.</span><span class="n">slugify_downcase</span><span class="p">(</span><span class="nb">name</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span>
</span></span><span class="line"><span class="cl">      <span class="n">model</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>A lógica é tal que key/secret novos são gerados apenas se os campos estiverem vazios e um slug novo é gerado apenas se o name tiver mudado. E é isso, eu disse que o código do model ia ser meio grande. Dá pra ver como usar as bibliotecas Slugger e SecureRandom que adicionamos no &ldquo;mix.exs&rdquo; antes.</p>
<p>Eu também quero adicionar o equivalente de um arquivo de seed do Rails pra criar uma aplicação de teste, assim quem está chegando agora sabe o que fazer. O Phoenix tem seeds e você pode implementar assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># priv/repo/seeds.exs</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">alias</span> <span class="no">ExPusherLite</span><span class="o">.</span><span class="n">App</span>
</span></span><span class="line"><span class="cl"><span class="k">alias</span> <span class="no">ExPusherLite</span><span class="o">.</span><span class="n">Repo</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># não usar o App.changeset pula todas as validações e gerações</span>
</span></span><span class="line"><span class="cl"><span class="no">Repo</span><span class="o">.</span><span class="n">insert!</span> <span class="o">%</span><span class="no">App</span><span class="p">{</span> <span class="ss">slug</span><span class="p">:</span> <span class="s2">&#34;test-app&#34;</span><span class="p">,</span> <span class="nb">name</span><span class="p">:</span> <span class="s2">&#34;Test App&#34;</span><span class="p">,</span> <span class="ss">key</span><span class="p">:</span> <span class="s2">&#34;test-app-fake-key&#34;</span><span class="p">,</span> <span class="ss">secret</span><span class="p">:</span> <span class="s2">&#34;test-app-fake-secret&#34;</span><span class="p">,</span> <span class="ss">active</span><span class="p">:</span> <span class="kp">true</span> <span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Lembra como detalhei o papel da função &ldquo;changeset/2&rdquo; em criar um changeset limpo e validado, que é só um Map? Você pode pular essa função inteira, montar seu próprio Map final na mão e passar pro Repo. O Repo não liga se o Map é válido ou não, ele simplesmente tenta inserir no banco. E neste caso o Model do App nos impede de hardcodar keys e secrets, então é assim que fazemos num arquivo de seed.</p>
<p>Dá pra rodar diretamente assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>mix run priv/repo/seeds.exs</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O AppController só precisa de 2 mudanças. A primeira é buscar o App pelo campo slug em vez do campo padrão &lsquo;id&rsquo;. É bem simples, basta substituir todas as chamadas de &ldquo;<tt>app = Repo.get!(App, id)</tt>&rdquo; por &ldquo;<tt>app = App.get_by_slug(id)</tt>&rdquo;, que é justamente por isso que implementamos essa função no model acima.</p>
<p>A segunda coisa é Autenticação.</p>
<h3>Adicionando Autenticação<span class="hx:absolute hx:-mt-20" id="adicionando-autenticação"></span>
    <a href="#adicionando-autentica%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Agora que temos um model App que consegue gerar UUIDs aleatórios seguros pra key e secret, vou adicionar um segundo nível de autenticação pra administradores conseguirem criar esses Apps.</p>
<p>Pra isso vou simplesmente hardcodar um secret no arquivo de config da aplicação como default de desenvolvimento. Assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># config/config.exs</span>
</span></span><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="n">config</span> <span class="ss">:ex_pusher_lite</span><span class="p">,</span> <span class="ss">:admin_authentication</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="ss">username</span><span class="p">:</span> <span class="s2">&#34;pusher_admin_username&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="ss">password</span><span class="p">:</span> <span class="s2">&#34;pusher_admin_password&#34;</span>
</span></span><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="n">import_config</span> <span class="s2">&#34;</span><span class="si">#{</span><span class="no">Mix</span><span class="o">.</span><span class="n">env</span><span class="si">}</span><span class="s2">.exs&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Você precisa adicionar esse bloco antes da função &ldquo;<tt>import_config</tt>&rdquo;. Aí você pode sobrescrever esses valores no arquivo &ldquo;config/prod.secret.exs&rdquo;, por exemplo, assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># config/prod.secret.exs</span>
</span></span><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="n">config</span> <span class="ss">:ex_pusher_lite</span><span class="p">,</span> <span class="ss">:admin_authentication</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="ss">username</span><span class="p">:</span> <span class="s2">&#34;14e86e5fee3335fa88b0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="ss">password</span><span class="p">:</span> <span class="s2">&#34;2b94ff0f07ce9769567f&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Claro, gere o seu próprio par seguro de username e password e substitua no ambiente de produção, se for usar isso pra valer. Pro Heroku ainda vamos ajustar isso um pouco mais, então fique atento.</p>
<p>Pra facilitar o processo, eu também adicionei o seguinte helper:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># lib/ex_pusher_lite.ex</span>
</span></span><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># Retorna o hash de Basic HTTP Auth de administração desta aplicação</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">admin_secret</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">admin_username</span> <span class="o">=</span> <span class="no">Application</span><span class="o">.</span><span class="n">get_env</span><span class="p">(</span><span class="ss">:ex_pusher_lite</span><span class="p">,</span> <span class="ss">:admin_authentication</span><span class="p">)</span><span class="o">[</span><span class="ss">:username</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">admin_password</span> <span class="o">=</span> <span class="no">Application</span><span class="o">.</span><span class="n">get_env</span><span class="p">(</span><span class="ss">:ex_pusher_lite</span><span class="p">,</span> <span class="ss">:admin_authentication</span><span class="p">)</span><span class="o">[</span><span class="ss">:password</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">secret</span> <span class="o">=</span> <span class="no">Base</span><span class="o">.</span><span class="n">encode64</span><span class="p">(</span><span class="s2">&#34;</span><span class="si">#{</span><span class="n">admin_username</span><span class="si">}</span><span class="s2">:</span><span class="si">#{</span><span class="n">admin_password</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>É assim que você busca os valores de configuração. Estou gerando uma string Base64 simples a partir do username concatenado com o password com uma vírgula, que é o que o Basic HTTP Auth requer. Vou usar esse hash de admin pro &ldquo;AppController&rdquo; e cada cliente vai precisar fornecer a key/secret da sua própria instância de App pra disparar o &ldquo;EventsController&rdquo;.</p>
<p>Pros dois controllers vou criar um único Plug de Autenticação, assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># lib/ex_pusher_lite/authentication.ex</span>
</span></span><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">ExPusherLite</span><span class="o">.</span><span class="n">Authentication</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">import</span> <span class="no">Plug</span><span class="o">.</span><span class="n">Conn</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">alias</span> <span class="no">ExPusherLite</span><span class="o">.</span><span class="n">App</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">init</span><span class="p">(</span><span class="n">assigns</span> <span class="p">\\</span> <span class="o">[</span><span class="ss">admin</span><span class="p">:</span> <span class="kp">false</span><span class="o">]</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="n">assigns</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">call</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="n">assigns</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">token</span> <span class="o">=</span>
</span></span><span class="line"><span class="cl">      <span class="k">if</span> <span class="n">assigns</span><span class="o">[</span><span class="ss">:admin</span><span class="o">]</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">        <span class="no">ExPusherLite</span><span class="o">.</span><span class="n">admin_secret</span>
</span></span><span class="line"><span class="cl">      <span class="k">else</span>
</span></span><span class="line"><span class="cl">        <span class="n">params</span> <span class="o">=</span> <span class="n">fetch_query_params</span><span class="p">(</span><span class="n">conn</span><span class="p">)</span><span class="o">.</span><span class="n">params</span>
</span></span><span class="line"><span class="cl">        <span class="n">params</span><span class="o">[</span><span class="s2">&#34;app_slug&#34;</span><span class="o">]</span> <span class="o">|&gt;</span> <span class="no">App</span><span class="o">.</span><span class="n">get_by_slug</span> <span class="o">|&gt;</span> <span class="no">App</span><span class="o">.</span><span class="n">hashed_secret</span>
</span></span><span class="line"><span class="cl">      <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Basic &#34;</span> <span class="o">&lt;&gt;</span> <span class="n">auth_token</span> <span class="o">=</span> <span class="n">hd</span><span class="p">(</span><span class="n">get_req_header</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="s2">&#34;authorization&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="no">Plug</span><span class="o">.</span><span class="n">Crypto</span><span class="o">.</span><span class="n">secure_compare</span><span class="p">(</span><span class="n">auth_token</span><span class="p">,</span> <span class="n">token</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="n">conn</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span>
</span></span><span class="line"><span class="cl">      <span class="n">conn</span> <span class="o">|&gt;</span> <span class="n">send_resp</span><span class="p">(</span><span class="mi">401</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">)</span> <span class="o">|&gt;</span> <span class="n">halt</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span> </span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Como expliquei em artigos anteriores, um Plug é como um Middleware encadeável do Rails ou até uma aplicação Rack. Ele precisa ter uma única &ldquo;<tt>call/2</tt>&rdquo; que recebe uma estrutura Plug.Conn e devolve ela de volta, permitindo formar uma cadeia/pipeline de Plugs.</p>
<p>Verificamos se queremos comparar com o token de Admin ou o token do App e aí pegamos o token de autorização Basic HTTP que está na estrutura de conexão do request HTTP (recuperamos valores individuais do header pela função &ldquo;<tt>get_req_header/2</tt>&rdquo;). Por fim, fazemos uma comparação segura entre os tokens.</p>
<p>Pra habilitar esse plug nos controllers basta adicionar assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># web/controllers/app_controller.ex</span>
</span></span><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">ExPusherLite</span><span class="o">.</span><span class="n">AppController</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">ExPusherLite</span><span class="o">.</span><span class="n">Web</span><span class="p">,</span> <span class="ss">:controller</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">alias</span> <span class="no">ExPusherLite</span><span class="o">.</span><span class="n">App</span>
</span></span><span class="line"><span class="cl">  <span class="n">plug</span> <span class="no">ExPusherLite</span><span class="o">.</span><span class="n">Authentication</span><span class="p">,</span> <span class="o">[</span><span class="ss">admin</span><span class="p">:</span> <span class="kp">true</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"> <span class="n">defmodule</span> <span class="no">ExPusherLite</span><span class="o">.</span><span class="n">EventsController</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">   <span class="n">use</span> <span class="no">ExPusherLite</span><span class="o">.</span><span class="n">Web</span><span class="p">,</span> <span class="ss">:controller</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">-</span>  <span class="n">plug</span> <span class="ss">:authenticate</span>
</span></span><span class="line"><span class="cl"><span class="o">+</span>  <span class="n">plug</span> <span class="no">ExPusherLite</span><span class="o">.</span><span class="n">Authentication</span>
</span></span><span class="line"><span class="cl">   <span class="o">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Na Parte 1 tínhamos um &ldquo;<tt>plug :authenticate</tt>&rdquo; mais simples no EventsController. Podemos remover ele e também a função &ldquo;<tt>authenticate/2</tt>&rdquo;. Refatoramos tudo pra uma função melhor que agora também serve à autenticação de administração, mas a ideia é a mesma.</p>
<p>É isso: o básico de autenticação de API. De novo, esta não é a melhor solução porque o par username/password vai na URL e fica aberto a ataques de man-in-the-middle. SSL só criptografa o body do HTTP mas a URL fica aberta.</p>
<p>Por exemplo, se um administrador quiser criar uma nova aplicação, precisa fazer assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>curl --data &#34;app[name]=foo-app&#34; http://pusher_admin_username:pusher_admin_password@localhost:4000/api/admin/apps</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E este seria um exemplo da representação JSON resultante do novo app:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>{&#34;data&#34;:{&#34;slug&#34;:&#34;foo-app&#34;,&#34;secret&#34;:&#34;8ef69064-0d7e-c9ef-ac14-b6b1db303e7a&#34;,&#34;name&#34;:&#34;foo-app&#34;,&#34;key&#34;:&#34;9400ad21-eed8-117a-bce5-845262e0a09e&#34;,&#34;id&#34;:5,&#34;active&#34;:true}}%</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Com essa nova key e secret em mãos, podemos atualizar nosso client demo pra usar o novo app.</p>
<h3>Configurando o Client Demo<span class="hx:absolute hx:-mt-20" id="configurando-o-client-demo"></span>
    <a href="#configurando-o-client-demo" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Vamos começar adicionando os detalhes apropriados da Aplicação no arquivo &ldquo;<tt>.env</tt>&rdquo;:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>PUSHER_URL: &#34;localhost:4000&#34;
PUSHER_APP_ID: &#34;foo-app&#34;
PUSHER_KEY: &#34;9400ad21-eed8-117a-bce5-845262e0a09e&#34;
PUSHER_SECRET: &#34;8ef69064-0d7e-c9ef-ac14-b6b1db303e7a&#34;
PUSHER_CHANNEL: &#34;foo-topic&#34;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Também temos que ajustar o &ldquo;<tt>config/secrets.yml</tt>&rdquo; pra refletir os novos metadados (development, test e production precisam seguir isso):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>development:
  secret_key_base: ded7c4a2a298c1b620e462b50c9ca6ccb60130e27968357e76cab73de9858f14556a26df885c8aa5004d0a7ca79c0438e618557275bdb28ba67a0ffb0c268056
  pusher_url: &lt;%= ENV[&#39;PUSHER_URL&#39;] %&gt;
  pusher_app_id: &lt;%= ENV[&#39;PUSHER_APP_ID&#39;] %&gt;
  pusher_key: &lt;%= ENV[&#39;PUSHER_KEY&#39;] %&gt;
  pusher_secret: &lt;%= ENV[&#39;PUSHER_SECRET&#39;] %&gt;
  pusher_channel: &lt;%= ENV[&#39;PUSHER_CHANNEL&#39;] %&gt;
  ...</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E podemos criar um initializer pra facilitar o uso desses metadados:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># config/initializers/pusher_lite.rb</span>
</span></span><span class="line"><span class="cl"><span class="k">module</span> <span class="nn">PusherLite</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">uri</span>
</span></span><span class="line"><span class="cl">    <span class="n">key</span>    <span class="o">=</span> <span class="no">Rails</span><span class="o">.</span><span class="n">application</span><span class="o">.</span><span class="n">secrets</span><span class="o">.</span><span class="n">pusher_key</span>
</span></span><span class="line"><span class="cl">    <span class="n">secret</span> <span class="o">=</span> <span class="no">Rails</span><span class="o">.</span><span class="n">application</span><span class="o">.</span><span class="n">secrets</span><span class="o">.</span><span class="n">pusher_secret</span>
</span></span><span class="line"><span class="cl">    <span class="n">app_id</span> <span class="o">=</span> <span class="no">Rails</span><span class="o">.</span><span class="n">application</span><span class="o">.</span><span class="n">secrets</span><span class="o">.</span><span class="n">pusher_app_id</span>
</span></span><span class="line"><span class="cl">    <span class="n">url</span>    <span class="o">=</span> <span class="no">Rails</span><span class="o">.</span><span class="n">application</span><span class="o">.</span><span class="n">secrets</span><span class="o">.</span><span class="n">pusher_url</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">uri</span> <span class="o">=</span> <span class="s2">&#34;http://</span><span class="si">#{</span><span class="n">key</span><span class="si">}</span><span class="s2">:</span><span class="si">#{</span><span class="n">secret</span><span class="si">}</span><span class="s2">@</span><span class="si">#{</span><span class="n">url</span><span class="si">}</span><span class="s2">/api/apps/</span><span class="si">#{</span><span class="n">app_id</span><span class="si">}</span><span class="s2">/events&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="no">URI</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">uri</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span> </span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>De novo, o app Rails vai disparar o servidor ExPusherLite usando o Basic HTTP Auth. Não se iluda achando que isso é &ldquo;seguro&rdquo;, apenas &ldquo;parece um pouco seguro pela obscuridade&rdquo;. Você foi avisado, espere os próximos artigos sobre o assunto. Mas isso é utilizável em ambientes controlados.</p>
<p>Pra finalizar os upgrades, temos que mudar o acesso do lado cliente aos novos metadados, primeiro mudando o layout da aplicação:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="c">&lt;!-- app/views/layouts/application.html.erb --&gt;</span>
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">+  <span class="p">&lt;</span><span class="nt">meta</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;pusher_host&#34;</span> <span class="na">content</span><span class="o">=</span><span class="s">&#34;&lt;%= Rails.application.secrets.pusher_url %&gt;&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">-  <span class="p">&lt;</span><span class="nt">meta</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;pusher_key&#34;</span> <span class="na">content</span><span class="o">=</span><span class="s">&#34;&lt;%= Rails.application.secrets.pusher_key %&gt;&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">+  <span class="p">&lt;</span><span class="nt">meta</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;pusher_app_id&#34;</span> <span class="na">content</span><span class="o">=</span><span class="s">&#34;&lt;%= Rails.application.secrets.pusher_app_id %&gt;&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">   <span class="p">&lt;</span><span class="nt">meta</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;pusher_channel&#34;</span> <span class="na">content</span><span class="o">=</span><span class="s">&#34;&lt;%= Rails.application.secrets.pusher_channel %&gt;&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">...</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O javascript &ldquo;<tt>index.es6</tt>&rdquo; busca esses meta headers, então temos que mudar lá também:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="err">#</span> <span class="nx">app</span><span class="o">/</span><span class="nx">assets</span><span class="o">/</span><span class="nx">javascripts</span><span class="o">/</span><span class="nx">application</span><span class="o">/</span><span class="nx">pages</span><span class="o">/</span><span class="nx">home</span><span class="o">/</span><span class="nx">index</span><span class="p">.</span><span class="nx">es6</span>
</span></span><span class="line"><span class="cl"><span class="p">...</span>
</span></span><span class="line"><span class="cl">     <span class="kd">let</span> <span class="nx">guardianToken</span> <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="s2">&#34;meta[name=guardian-token]&#34;</span><span class="p">).</span><span class="nx">attr</span><span class="p">(</span><span class="s2">&#34;content&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">     <span class="kd">let</span> <span class="nx">csrfToken</span>     <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="s2">&#34;meta[name=guardian-csrf]&#34;</span><span class="p">).</span><span class="nx">attr</span><span class="p">(</span><span class="s2">&#34;content&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> 
</span></span><span class="line"><span class="cl"><span class="o">+</span>    <span class="kd">let</span> <span class="nx">pusherHost</span>    <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="s2">&#34;meta[name=pusher_host]&#34;</span><span class="p">).</span><span class="nx">attr</span><span class="p">(</span><span class="s2">&#34;content&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="o">-</span>    <span class="kd">let</span> <span class="nx">pusherKey</span>     <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="s2">&#34;meta[name=pusher_key]&#34;</span><span class="p">).</span><span class="nx">attr</span><span class="p">(</span><span class="s2">&#34;content&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="o">+</span>    <span class="kd">let</span> <span class="nx">pusherApp</span>     <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="s2">&#34;meta[name=pusher_app_id]&#34;</span><span class="p">).</span><span class="nx">attr</span><span class="p">(</span><span class="s2">&#34;content&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">     <span class="kd">let</span> <span class="nx">pusherChannel</span> <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="s2">&#34;meta[name=pusher_channel]&#34;</span><span class="p">).</span><span class="nx">attr</span><span class="p">(</span><span class="s2">&#34;content&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> 
</span></span><span class="line"><span class="cl"><span class="o">-</span>    <span class="kd">let</span> <span class="nx">socket</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Socket</span><span class="p">(</span><span class="s2">&#34;ws://localhost:4000/socket&#34;</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="o">+</span>    <span class="kd">let</span> <span class="nx">socket</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Socket</span><span class="p">(</span><span class="sb">`ws://</span><span class="si">${</span><span class="nx">pusherHost</span><span class="si">}</span><span class="sb">/socket`</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">       <span class="nx">params</span><span class="o">:</span> <span class="p">{</span> <span class="nx">guardian_token</span><span class="o">:</span> <span class="nx">guardianToken</span><span class="p">,</span> <span class="nx">csrf_token</span><span class="o">:</span> <span class="nx">csrfToken</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">     <span class="p">})</span>
</span></span><span class="line"><span class="cl">     <span class="nx">socket</span><span class="p">.</span><span class="nx">connect</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"> 
</span></span><span class="line"><span class="cl">     <span class="c1">// Agora que está conectado, dá pra entrar em canais com um topic:
</span></span></span><span class="line"><span class="cl"><span class="o">-</span>    <span class="kd">let</span> <span class="nx">channel</span> <span class="o">=</span> <span class="nx">socket</span><span class="p">.</span><span class="nx">channel</span><span class="p">(</span><span class="nx">pusherChannel</span><span class="p">,</span> <span class="p">{})</span>
</span></span><span class="line"><span class="cl"><span class="o">+</span>    <span class="kd">let</span> <span class="nx">channel</span> <span class="o">=</span> <span class="nx">socket</span><span class="p">.</span><span class="nx">channel</span><span class="p">(</span><span class="sb">`public:</span><span class="si">${</span><span class="nx">pusherApp</span><span class="si">}</span><span class="sb">`</span><span class="p">,</span> <span class="p">{})</span>
</span></span><span class="line"><span class="cl">     <span class="nx">channel</span><span class="p">.</span><span class="nx">join</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">       <span class="p">.</span><span class="nx">receive</span><span class="p">(</span><span class="s2">&#34;ok&#34;</span><span class="p">,</span> <span class="nx">resp</span> <span class="p">=&gt;</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">&#34;Joined successfully&#34;</span><span class="p">,</span> <span class="nx">resp</span><span class="p">)</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl">       <span class="p">.</span><span class="nx">receive</span><span class="p">(</span><span class="s2">&#34;error&#34;</span><span class="p">,</span> <span class="nx">resp</span> <span class="p">=&gt;</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">&#34;Unable to join&#34;</span><span class="p">,</span> <span class="nx">resp</span><span class="p">)</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl"> 
</span></span><span class="line"><span class="cl"><span class="o">-</span>    <span class="nx">channel</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s2">&#34;msg&#34;</span><span class="p">,</span> <span class="nx">data</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="o">+</span>    <span class="nx">channel</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="sb">`</span><span class="si">${</span><span class="nx">pusherChannel</span><span class="si">}</span><span class="sb">:msg`</span><span class="p">,</span> <span class="nx">data</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">       <span class="kd">let</span> <span class="nx">new_line</span> <span class="o">=</span> <span class="sb">`&lt;p&gt;&lt;strong&gt;</span><span class="si">${</span><span class="nx">data</span><span class="p">.</span><span class="nx">name</span><span class="si">}</span><span class="sb">&lt;strong&gt;: </span><span class="si">${</span><span class="nx">data</span><span class="p">.</span><span class="nx">message</span><span class="si">}</span><span class="sb">&lt;/p&gt;`</span>
</span></span><span class="line"><span class="cl">       <span class="nx">$</span><span class="p">(</span><span class="s2">&#34;.message-receiver&#34;</span><span class="p">).</span><span class="nx">append</span><span class="p">(</span><span class="nx">new_line</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">     <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="o">+</span>
</span></span><span class="line"><span class="cl"><span class="o">+</span>    <span class="nx">channel</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s2">&#34;msg&#34;</span><span class="p">,</span> <span class="nx">data</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="o">+</span>      <span class="kd">let</span> <span class="nx">new_line</span> <span class="o">=</span> <span class="sb">`&lt;p&gt;&lt;strong&gt;Broadcast to all channels&lt;/strong&gt;: </span><span class="si">${</span><span class="nx">data</span><span class="p">.</span><span class="nx">message</span><span class="si">}</span><span class="sb">&lt;/p&gt;`</span>
</span></span><span class="line"><span class="cl"><span class="o">+</span>      <span class="nx">$</span><span class="p">(</span><span class="s2">&#34;.message-receiver&#34;</span><span class="p">).</span><span class="nx">append</span><span class="p">(</span><span class="nx">new_line</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="o">+</span>    <span class="p">})</span>
</span></span><span class="line"><span class="cl">   <span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Uma modificação importante em relação à Parte 1 é que o host do WebSocket estava hardcoded em &ldquo;localhost&rdquo; e aqui estamos tornando ele configurável via meta tags. No momento, pros testes em localhost, estamos usando o protocolo &ldquo;ws://&rdquo; puro, mas quando fizermos deploy no Heroku vamos mudar pra &ldquo;wss://&rdquo; pra ter SSL. Mesma coisa pro initializer &ldquo;PusherLite&rdquo;. Tenha isso em mente.</p>
<p>Agora ele está se inscrevendo num formato diferente de topic/channel. Na Parte 1 era algo como: &ldquo;<tt>public:test_channel</tt>&rdquo;, e agora estamos escutando em &ldquo;<tt>public:foo-app</tt>&rdquo;, então a aplicação é o &ldquo;topic&rdquo; da inscrição no Websocket.</p>
<p>Aí estamos mudando o socket listener pra escutar 2 events diferentes. O primeiro está no formato &ldquo;<tt>test_channel:msg</tt>&rdquo;. É assim que agora temos que enviar mensagens pra um &ldquo;channel&rdquo; específico dentro de um &ldquo;app/topic&rdquo;.</p>
<p>E por último ainda escutamos o velho event &ldquo;msg&rdquo;, mas ele agora serve como um event de &ldquo;broadcast&rdquo; pra todos os clientes conectados inscritos nessa Aplicação &ldquo;foo-app&rdquo; em particular. Agora os clientes web podem escutar &ldquo;channels&rdquo; específicos dentro do &ldquo;app&rdquo; mas também receber mensagens de &ldquo;broadcast&rdquo; globais. Isso é uma melhoria grande e quase não exigiu coisa do lado do Javascript.</p>
<p>Mas o que mais é preciso pra fazer esse sistema <strong>&ldquo;channel-only and broadcast&rdquo;</strong> funcionar? Primeiro, começamos mudando o formulário web pra deixar o usuário escolher entre enviar uma mensagem só de canal ou um broadcast, assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="c">&lt;!-- app/views/home/index.html.erb --&gt;</span>
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">     <span class="err">&lt;</span>%= f.text_field :name, placeholder: &#34;Name&#34; %&gt;
</span></span><span class="line"><span class="cl">     <span class="err">&lt;</span>%= f.text_field :message, placeholder: &#34;Message&#34; %&gt;
</span></span><span class="line"><span class="cl">+    <span class="err">&lt;</span>%= f.check_box :broadcast %&gt;
</span></span><span class="line"><span class="cl">     <span class="err">&lt;</span>%= f.submit &#34;Send message&#34;, class: &#34;pure-button pure-button-primary&#34; %&gt;
</span></span><span class="line"><span class="cl">   <span class="p">&lt;/</span><span class="nt">fieldset</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">...</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora o EventsController precisa aceitar esse novo parâmetro:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># app/controllers/events_controller.rb</span>
</span></span><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">event_params</span>
</span></span><span class="line"><span class="cl">    <span class="n">params</span><span class="o">.</span><span class="n">require</span><span class="p">(</span><span class="ss">:pusher_event</span><span class="p">)</span><span class="o">.</span><span class="n">permit</span><span class="p">(</span><span class="ss">:name</span><span class="p">,</span> <span class="ss">:message</span><span class="p">,</span> <span class="ss">:broadcast</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Por fim, o Model precisa usar essa nova informação antes de fazer o post pro servidor ExPusherLite:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># app/models/pusher_event.rb</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">PusherEvent</span>
</span></span><span class="line"><span class="cl">  <span class="kp">include</span> <span class="no">ActiveModel</span><span class="o">::</span><span class="no">Model</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kp">attr_accessor</span> <span class="ss">:name</span><span class="p">,</span> <span class="ss">:message</span><span class="p">,</span> <span class="ss">:broadcast</span>
</span></span><span class="line"><span class="cl">  <span class="n">validates</span> <span class="ss">:name</span><span class="p">,</span> <span class="ss">:message</span><span class="p">,</span> <span class="ss">presence</span><span class="p">:</span> <span class="kp">true</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">save</span>
</span></span><span class="line"><span class="cl">    <span class="n">topic</span> <span class="o">=</span> <span class="k">if</span> <span class="n">broadcast</span> <span class="o">==</span> <span class="s2">&#34;1&#34;</span>
</span></span><span class="line"><span class="cl">              <span class="s2">&#34;#general&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="k">else</span>
</span></span><span class="line"><span class="cl">              <span class="no">Rails</span><span class="o">.</span><span class="n">application</span><span class="o">.</span><span class="n">secrets</span><span class="o">.</span><span class="n">pusher_channel</span>
</span></span><span class="line"><span class="cl">            <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="no">Net</span><span class="o">::</span><span class="no">HTTP</span><span class="o">.</span><span class="n">post_form</span><span class="p">(</span><span class="no">PusherLite</span><span class="o">.</span><span class="n">uri</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;topic&#34;</span> <span class="o">=&gt;</span> <span class="n">topic</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;event&#34;</span> <span class="o">=&gt;</span> <span class="s2">&#34;msg&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;scope&#34;</span> <span class="o">=&gt;</span> <span class="s2">&#34;public&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;payload&#34;</span> <span class="o">=&gt;</span> <span class="p">{</span><span class="s2">&#34;name&#34;</span> <span class="o">=&gt;</span> <span class="nb">name</span><span class="p">,</span> <span class="s2">&#34;message&#34;</span> <span class="o">=&gt;</span> <span class="n">message</span><span class="p">}</span><span class="o">.</span><span class="n">to_json</span>
</span></span><span class="line"><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Estou apenas assumindo uma string hardcoded &ldquo;<tt>#general</tt>&rdquo; pra servir de gatilho de broadcast pro servidor. Agora temos que fazer o servidor aceitar esse novo schema de protocolo, então vamos voltar pro Elixir.</p>
<p>Primeiro temos que começar com a contraparte do trigger POST anterior, <tt>ExPusherLite.EventsController</tt>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># web/controllers/events_controller.ex</span>
</span></span><span class="line"><span class="cl"> <span class="n">defmodule</span> <span class="no">ExPusherLite</span><span class="o">.</span><span class="n">EventsController</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">   <span class="n">use</span> <span class="no">ExPusherLite</span><span class="o">.</span><span class="n">Web</span><span class="p">,</span> <span class="ss">:controller</span>
</span></span><span class="line"><span class="cl"> 
</span></span><span class="line"><span class="cl"><span class="o">-</span>  <span class="n">plug</span> <span class="ss">:authenticate</span>
</span></span><span class="line"><span class="cl"><span class="o">+</span>  <span class="n">plug</span> <span class="no">ExPusherLite</span><span class="o">.</span><span class="n">Authentication</span>
</span></span><span class="line"><span class="cl"> 
</span></span><span class="line"><span class="cl"><span class="o">-</span>  <span class="k">def</span> <span class="nf">create</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="n">params</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl"><span class="o">-</span>    <span class="n">topic</span> <span class="o">=</span> <span class="n">params</span><span class="o">[</span><span class="s2">&#34;topic&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="o">-</span>    <span class="n">event</span> <span class="o">=</span> <span class="n">params</span><span class="o">[</span><span class="s2">&#34;event&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="o">+</span>  <span class="k">def</span> <span class="nf">create</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="sx">%{&#34;app_slug&#34; =&gt; app_slug, &#34;event&#34; =&gt; event, &#34;topic&#34; =&gt; topic, &#34;scope&#34; =&gt; scope}</span> <span class="o">=</span> <span class="n">params</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">     <span class="n">message</span> <span class="o">=</span> <span class="p">(</span><span class="n">params</span><span class="o">[</span><span class="s2">&#34;payload&#34;</span><span class="o">]</span> <span class="o">||</span> <span class="s2">&#34;{}&#34;</span><span class="p">)</span> <span class="o">|&gt;</span> <span class="no">Poison</span><span class="o">.</span><span class="n">decode!</span>
</span></span><span class="line"><span class="cl"><span class="o">-</span>    <span class="no">ExPusherLite</span><span class="o">.</span><span class="n">Endpoint</span><span class="o">.</span><span class="n">broadcast!</span> <span class="n">topic</span><span class="p">,</span> <span class="n">event</span><span class="p">,</span> <span class="n">message</span>
</span></span><span class="line"><span class="cl"><span class="o">+</span>    <span class="n">topic_event</span> <span class="o">=</span>
</span></span><span class="line"><span class="cl"><span class="o">+</span>      <span class="k">if</span> <span class="n">topic</span> <span class="o">==</span> <span class="s2">&#34;#general&#34;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl"><span class="o">+</span>        <span class="n">event</span>
</span></span><span class="line"><span class="cl"><span class="o">+</span>      <span class="k">else</span>
</span></span><span class="line"><span class="cl"><span class="o">+</span>        <span class="s2">&#34;</span><span class="si">#{</span><span class="n">topic</span><span class="si">}</span><span class="s2">:</span><span class="si">#{</span><span class="n">event</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="o">+</span>      <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="o">+</span>    <span class="no">ExPusherLite</span><span class="o">.</span><span class="n">Endpoint</span><span class="o">.</span><span class="n">broadcast!</span> <span class="s2">&#34;</span><span class="si">#{</span><span class="n">scope</span><span class="si">}</span><span class="s2">:</span><span class="si">#{</span><span class="n">app_slug</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span> <span class="n">topic_event</span><span class="p">,</span> <span class="n">message</span>
</span></span><span class="line"><span class="cl">     <span class="n">json</span> <span class="n">conn</span><span class="p">,</span> <span class="sx">%{}</span>
</span></span><span class="line"><span class="cl">   <span class="k">end</span>
</span></span><span class="line"><span class="cl">   <span class="o">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>A primeira diferença é que estou fazendo pattern match direto dos argumentos pras variáveis &ldquo;topic&rdquo; e &ldquo;event&rdquo;. Essa função também sabe da string &ldquo;<tt>#general</tt>&rdquo; que o cliente pode enviar pra indicar um broadcast global do app. E o novo topic é a concatenação de &ldquo;topic&rdquo; e &ldquo;event&rdquo; pra permitir mensagens &ldquo;channel-only&rdquo;.</p>
<p>Pra ligar tudo isso ao handler de WebSocket, temos que fazer as seguintes mudanças:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># web/channels/room_channel.ex</span>
</span></span><span class="line"><span class="cl"><span class="o">-</span>  <span class="k">def</span> <span class="nf">handle_in</span><span class="p">(</span><span class="s2">&#34;msg&#34;</span><span class="p">,</span> <span class="n">payload</span><span class="p">,</span> <span class="n">socket</span> <span class="o">=</span> <span class="sx">%{ topic: &#34;public:&#34; &lt;&gt; _ }</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl"><span class="o">-</span>    <span class="n">broadcast</span> <span class="n">socket</span><span class="p">,</span> <span class="s2">&#34;msg&#34;</span><span class="p">,</span> <span class="n">payload</span>
</span></span><span class="line"><span class="cl"><span class="o">+</span>  <span class="k">def</span> <span class="nf">handle_in</span><span class="p">(</span><span class="n">topic_event</span><span class="p">,</span> <span class="n">payload</span><span class="p">,</span> <span class="n">socket</span> <span class="o">=</span> <span class="sx">%{ topic: &#34;public:&#34; &lt;&gt; _ }</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl"><span class="o">+</span>    <span class="n">broadcast</span> <span class="n">socket</span><span class="p">,</span> <span class="n">topic_event</span><span class="p">,</span> <span class="n">payload</span>
</span></span><span class="line"><span class="cl">     <span class="p">{</span> <span class="ss">:noreply</span><span class="p">,</span> <span class="n">socket</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">   <span class="k">end</span>
</span></span><span class="line"><span class="cl"> 
</span></span><span class="line"><span class="cl"><span class="o">-</span>  <span class="k">def</span> <span class="nf">handle_in</span><span class="p">(</span><span class="s2">&#34;msg&#34;</span><span class="p">,</span> <span class="n">payload</span><span class="p">,</span> <span class="n">socket</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl"><span class="o">+</span>  <span class="k">def</span> <span class="nf">handle_in</span><span class="p">(</span><span class="n">topic_event</span><span class="p">,</span> <span class="n">payload</span><span class="p">,</span> <span class="n">socket</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">     <span class="n">claims</span> <span class="o">=</span> <span class="no">Guardian</span><span class="o">.</span><span class="n">Channel</span><span class="o">.</span><span class="n">claims</span><span class="p">(</span><span class="n">socket</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">     <span class="k">if</span> <span class="n">permitted_topic?</span><span class="p">(</span><span class="n">claims</span><span class="o">[</span><span class="ss">:publish</span><span class="o">]</span><span class="p">,</span> <span class="n">socket</span><span class="o">.</span><span class="n">topic</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl"><span class="o">-</span>      <span class="n">broadcast</span> <span class="n">socket</span><span class="p">,</span> <span class="s2">&#34;msg&#34;</span><span class="p">,</span> <span class="n">payload</span>
</span></span><span class="line"><span class="cl"><span class="o">+</span>      <span class="n">broadcast</span> <span class="n">socket</span><span class="p">,</span> <span class="n">topic_event</span><span class="p">,</span> <span class="n">payload</span>
</span></span><span class="line"><span class="cl">       <span class="p">{</span> <span class="ss">:noreply</span><span class="p">,</span> <span class="n">socket</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">   <span class="o">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora o Channel já não faz pattern match num event específico, deixa passar sem mais validação, confiando que o EventsController está fazendo a coisa certa. Talvez eu volte nessa parte pra melhorias no futuro.</p>
<h3>Fazendo deploy do nosso primeiro app Phoenix no Heroku!<span class="hx:absolute hx:-mt-20" id="fazendo-deploy-do-nosso-primeiro-app-phoenix-no-heroku"></span>
    <a href="#fazendo-deploy-do-nosso-primeiro-app-phoenix-no-heroku" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Nesta seção vamos só seguir a <a href="http://www.phoenixframework.org/docs/heroku"target="_blank" rel="noopener">documentação oficial</a>, então leia lá se quiser mais detalhes.</p>
<p>Vamos começar:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>heroku apps:create your-expusherlite --buildpack &#34;https://github.com/HashNuke/heroku-buildpack-elixir.git&#34;
heroku buildpacks:add https://github.com/gjaldon/heroku-buildpack-phoenix-static.git</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Estou nomeando a aplicação como &ldquo;your-expusherlite&rdquo; mas você deve trocar pelo seu próprio nome, claro. E o resto dos dados de configuração são todos exemplos que você precisa mudar pras suas próprias necessidades.</p>
<p>O Heroku depende de variáveis de ambiente. Então começamos apagando &ldquo;<tt>config/prod.secret.exs</tt>&rdquo; e mudando o &ldquo;<tt>config/prod.exs</tt>&rdquo; pra ficar assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">config</span> <span class="ss">:ex_pusher_lite</span><span class="p">,</span> <span class="no">ExPusherLite</span><span class="o">.</span><span class="n">Endpoint</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="ss">http</span><span class="p">:</span> <span class="o">[</span><span class="ss">port</span><span class="p">:</span> <span class="p">{</span><span class="ss">:system</span><span class="p">,</span> <span class="s2">&#34;PORT&#34;</span><span class="p">}</span><span class="o">]</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="ss">url</span><span class="p">:</span> <span class="o">[</span><span class="ss">scheme</span><span class="p">:</span> <span class="s2">&#34;https&#34;</span><span class="p">,</span> <span class="ss">host</span><span class="p">:</span> <span class="s2">&#34;your-expusherlite.herokuapp.com&#34;</span><span class="p">,</span> <span class="ss">port</span><span class="p">:</span> <span class="mi">443</span><span class="o">]</span><span class="p">,</span> <span class="ss">force_ssl</span><span class="p">:</span> <span class="o">[</span><span class="ss">rewrite_on</span><span class="p">:</span> <span class="o">[</span><span class="ss">:x_forwarded_proto</span><span class="o">]]</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="ss">cache_static_manifest</span><span class="p">:</span> <span class="s2">&#34;priv/static/manifest.json&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="ss">secret_key_base</span><span class="p">:</span> <span class="no">System</span><span class="o">.</span><span class="n">get_env</span><span class="p">(</span><span class="s2">&#34;SECRET_KEY_BASE&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Configure seu banco</span>
</span></span><span class="line"><span class="cl"><span class="n">config</span> <span class="ss">:ex_pusher_lite</span><span class="p">,</span> <span class="no">ExPusherLite</span><span class="o">.</span><span class="n">Repo</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="ss">adapter</span><span class="p">:</span> <span class="no">Ecto</span><span class="o">.</span><span class="n">Adapters</span><span class="o">.</span><span class="n">Postgres</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="ss">url</span><span class="p">:</span> <span class="no">System</span><span class="o">.</span><span class="n">get_env</span><span class="p">(</span><span class="s2">&#34;DATABASE_URL&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="ss">pool_size</span><span class="p">:</span> <span class="mi">20</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">config</span> <span class="ss">:ex_pusher_lite</span><span class="p">,</span> <span class="ss">:admin_authentication</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="ss">username</span><span class="p">:</span> <span class="no">System</span><span class="o">.</span><span class="n">get_env</span><span class="p">(</span><span class="s2">&#34;PUSHER_ADMIN_USERNAME&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="ss">password</span><span class="p">:</span> <span class="no">System</span><span class="o">.</span><span class="n">get_env</span><span class="p">(</span><span class="s2">&#34;PUSHER_ADMIN_PASSWORD&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># remova esta linha:</span>
</span></span><span class="line"><span class="cl"><span class="c1"># import_config &#34;prod.secret.exs&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora temos que configurar as variáveis de ambiente &ldquo;SECRET_KEY_BASE&rdquo;, &ldquo;PUSHER_ADMIN_USERNAME&rdquo; e &ldquo;PUSHER_ADMIN_PASSWORD&rdquo;. Use o &ldquo;<tt>mix phoenix.gen.secret</tt>&rdquo; que já vem incluído pra gerar elas.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>heroku config:set SECRET_KEY_BASE=&#34;`mix phoenix.gen.secret`&#34;
heroku config:set PUSHER_ADMIN_USERNAME=&#34;FPO0QUkqbAP6EGjElqBzDQuMs8bhFS3&#34;
heroku config:set PUSHER_ADMIN_PASSWORD=&#34;n78DPGmK3DBQy8YAVyshiGqcXjjSXSD&#34;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Aí é só esperar o velho e bom &ldquo;<tt>git push heroku master</tt>&rdquo; terminar de compilar tudo da primeira vez. E como é o primeiro deploy, não esqueça de rodar &ldquo;<tt>heroku run mix ecto.migrate</tt>&rdquo; pra criar a tabela do banco.</p>
<p>Agora, se eu fiz tudo direito, como Administrador que conhece os secrets hardcoded acima eu deveria conseguir criar uma nova Aplicação assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>curl --data &#34;app[name]=shiny-new-app&#34; https://FPO0QUkqbAP6EGjElqBzDQuMs8bhFS3:n78DPGmK3DBQy8YAVyshiGqcXjjSXSD@your-expusherlite.herokuapp.com/api/admin/apps</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E foi este o resultado que eu obtive!</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>{&#34;data&#34;:{&#34;slug&#34;:&#34;shiny-new-app&#34;,&#34;secret&#34;:&#34;42560373-0fe1-506e-28ca-35ab5221fb3d&#34;,&#34;name&#34;:&#34;shiny-new-app&#34;,&#34;key&#34;:&#34;958c16e7-ab93-dac0-0fc6-6cb864e26358&#34;,&#34;id&#34;:1,&#34;active&#34;:true}}</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Beleza, agora que temos uma key e secret válidos da Aplicação podemos configurar nosso Rails Client Demo e fazer deploy dele no Heroku também.</p>
<h3>Fazendo deploy do Cliente Rails no Heroku<span class="hx:absolute hx:-mt-20" id="fazendo-deploy-do-cliente-rails-no-heroku"></span>
    <a href="#fazendo-deploy-do-cliente-rails-no-heroku" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Esta é uma aplicação Rails simples, podemos só criar o app e fazer deploy direto:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>heroku create your-expusherlite-client
heroku config:set PUSHER_URL=your-expusherlite.herokuapp.com
heroku config:set PUSHER_APP_ID=shiny-new-app
heroku config:set PUSHER_KEY=958c16e7-ab93-dac0-0fc6-6cb864e26358
heroku config:set PUSHER_SECRET=42560373-0fe1-506e-28ca-35ab5221fb3d
heroku config:set PUSHER_CHANNEL=shiny-new-topic
git push heroku master</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Estou assumindo que os leitores deste post já sabem como configurar uma app Rails apropriadamente pro Heroku. Só pra mencionar, eu configurei essa app com as gems 12 factor e Puma e adicionei um Procfile apropriado. Outra mudança bem pequena foi alterar o initializer &ldquo;pusher_lite.rb&rdquo; pra criar uma URI com &ldquo;https&rdquo; porque o ExPusherLite que fizemos deploy em produção exige SSL por padrão.</p>
<p>Tem mais um detalhe. Por ser conduzido por programadores web experientes, eles garantiram que, ao contrário deste exercício bem cru aqui, o framework Phoenix em si é seguro. Um exemplo é proibir conexões Websocket vindas de hosts diferentes.</p>
<p>Por padrão, o Socket do &ldquo;phoenix.js&rdquo; vai falhar a conexão quando tentarmos conectar do host do app Rails &ldquo;your-expusherlite-client.herokuapp.com&rdquo; pro app Phoenix em &ldquo;your-expusherlite.herokuapp.com&rdquo; com o seguinte erro:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>WebSocket connection to &#39;wss://your-expusherlite.herokuapp.com/socket/websocket?guardian_token=N_YCG6hGK7…iOlsicHVibGljOioiXX0._j6s2LiaKde9rBhnTMxDkm0XV5u89pNh1AdLFY6Rlt8&amp;vsn=1.0.0&#39; failed: Error during WebSocket handshake: Unexpected response code: 403</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E no log do Phoenix vamos ver esta mensagem bem útil:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>[error] Could not check origin for Phoenix.Socket transport.

This happens when you are attempting a socket connection to
a different host than the one configured in your config/
files. For example, in development the host is configured
to &#34;localhost&#34; but you may be trying to access it from
&#34;127.0.0.1&#34;. To fix this issue, you may either:

  1. update [url: [host: ...]] to your actual host in the
     config file for your current environment (recommended)

  2. pass the :check_origin option when configuring your
     endpoint or when configuring the transport in your
     UserSocket module, explicitly outlining which origins
     are allowed:

        check_origin: [&#34;https://example.com&#34;,
                       &#34;//another.com:888&#34;, &#34;//other.com&#34;]</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>A menos que você saiba o que é um <a href="https://www.christian-schneider.net/CrossSiteWebSocketHijacking.html"target="_blank" rel="noopener">Cross-Site Web Socket Hijacking</a>, é melhor deixar as configurações padrão como estão. Numa app Phoenix novinha, a parte Web vai conectar no Web Socket dentro da mesma app e, portanto, no mesmo host, então isso não é problema.</p>
<p>No nosso caso eu estou fazendo um micro-serviço separado pra mimetizar o comportamento do Pusher.com, então ele precisa aceitar conexões de Web Socket de hosts diferentes.</p>
<p>Se você controla as aplicações que vão ser criadas, provavelmente vai preferir fazer a config &ldquo;<tt>check_origin</tt>&rdquo; ler do banco os hosts exatos. Como feature pra próxima vez eu poderia adicionar um campo &ldquo;host&rdquo; no model &ldquo;App&rdquo; e usar pra validar as conexões na configuração do transport. Por enquanto vou só fazer ele aceitar qualquer host:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># web/channels/user_socket.ex
defmodule ExPusherLite.UserSocket do
  use Phoenix.Socket

  ## Channels
  channel &#34;*&#34;, ExPusherLite.RoomChannel

  ## Transports
  transport :websocket, Phoenix.Transports.WebSocket, check_origin: false
  ...</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E é isso! Agora a app Rails deve conseguir conectar e enviar mensagens! E você deve conseguir criar quantos apps quiser e ligar todos eles a este mesmo serviço.</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/534/big_Screenshot_from_2015-12-14_14_58_22.png" alt="Final Heroku Client"  loading="lazy" /></p>
<h3>Conclusão<span class="hx:absolute hx:-mt-20" id="conclusão"></span>
    <a href="#conclus%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Neste momento, temos um clone funcional, embora bem cru, do Pusher.com que serve pra um monte de casos de uso onde o Pusher.com seria usado.</p>
<p>Como avisei várias vezes, a parte de segurança ainda está bamba e precisa de trabalho. Vou ainda estender o que o Daniel começou com o Guardian pra também autenticar usuários do Web Socket em canais privados. E o core também deve receber capacidades de auditoria e relatório (pra reportar uso, número de conexões ativas, throughput de eventos, manter pelo menos um histórico curto de eventos pras conexões novas conseguirem recuperar as últimas mensagens enviadas, e por aí vai).</p>
<p>Mas daqui pra frente é só uma questão de adicionar features a um core que já funciona. E isso é nada mais que Phoenix out-of-the-box sem grande coisa adicionada por cima! Diz muito sobre o atual estado de maturidade desse framework tão capaz.</p>
<p>Em termos de performance, pra este exemplo simplíssimo, eu subi 1 dyno free do Heroku pra cada app.</p>
<p>A app Rails consegue responder à interface front-end em torno de 2ms. E o job do Sucker Punch - que faz o HTTP POST pesado pro ExPusherLite - leva na ordem de 30ms ou menos.</p>
<p>O servidor Phoenix recebe o HTTP POST e faz o broadcast em torno de menos de 6ms. Também bem rápido. Os tempos vão variar bastante porque eu acredito que o dyno free é não só lento mas também fica em metal boxes muito compartilhadas, sofrendo impacto de outras apps vizinhas rodando na mesma máquina.</p>
<p>Como já temos uma API administrativa pra criar e gerenciar apps (criar novas, deletar, atualizar etc.) já dá pra criar uma aplicação separada em qualquer outro framework pra fazer um dashboard pra admins ou uma front-end self-service pros desenvolvedores registrarem novas apps e receberem o par key/secret pra adicionar nas suas próprias aplicações.</p>
<p>Tanto o servidor ExPusherLite quanto o cliente demo estão em deploy no Heroku e você pode testar o cliente agora mesmo <a href="https://expusherlite-client.herokuapp.com/"target="_blank" rel="noopener">clicando aqui</a>. As keys de admin são diferentes das que mostrei neste post, claro, então você não vai conseguir criar apps novas, mas pode fazer deploy você mesmo no seu próprio ambiente.</p>
]]></content:encoded><category>elixir-e-erlang</category><category>sistemas-distribuidos</category><category>devops</category></item><item><title>Ex Pusher Lite - Parte 1: Phoenix Channels e aplicações Rails</title><link>https://akitaonrails.github.io/2015/12/09/ex-pusher-lite-parte-1-phoenix-channels-e-aplicacoes-rails/</link><guid isPermaLink="true">https://akitaonrails.github.io/2015/12/09/ex-pusher-lite-parte-1-phoenix-channels-e-aplicacoes-rails/</guid><pubDate>Wed, 09 Dec 2015 18:39:00 -0200</pubDate><description>&lt;p&gt;Finalmente, depois de um bom período de exercícios (e muito blog post!) vou começar a implementar a aplicação Elixir que eu queria desde o começo.&lt;/p&gt;
&lt;p&gt;Como desenvolvedor Rails, existem algumas coisas que a gente simplesmente não consegue fazer em Rails. Uma delas é lidar com mensageria em tempo real.&lt;/p&gt;
&lt;p&gt;O Rails 5 vai trazer o &lt;a href="https://github.com/rails/actioncable"target="_blank" rel="noopener"&gt;Action Cable&lt;/a&gt;, e talvez seja bom o suficiente para a maioria dos casos. Ele usa o &lt;a href="https://github.com/faye/faye"target="_blank" rel="noopener"&gt;Faye&lt;/a&gt;, que por sua vez é baseado em Eventmachine. Você pode implementar uma solução suficientemente boa para Websockets usando Faye na sua aplicação Rails 4.2 agora mesmo.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Finalmente, depois de um bom período de exercícios (e muito blog post!) vou começar a implementar a aplicação Elixir que eu queria desde o começo.</p>
<p>Como desenvolvedor Rails, existem algumas coisas que a gente simplesmente não consegue fazer em Rails. Uma delas é lidar com mensageria em tempo real.</p>
<p>O Rails 5 vai trazer o <a href="https://github.com/rails/actioncable"target="_blank" rel="noopener">Action Cable</a>, e talvez seja bom o suficiente para a maioria dos casos. Ele usa o <a href="https://github.com/faye/faye"target="_blank" rel="noopener">Faye</a>, que por sua vez é baseado em Eventmachine. Você pode implementar uma solução suficientemente boa para Websockets usando Faye na sua aplicação Rails 4.2 agora mesmo.</p>
<p>Outra opção é evitar o trabalho todo e usar um serviço de mensageria. Uma opção que sempre recomendo, sem fricção nenhuma, é o <a href="https://pusher.com/"target="_blank" rel="noopener">Pusher.com</a>.</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/533/big_Screenshot_from_2015-12-09_18_41_22.png" alt="Chat Example"  loading="lazy" /></p>
<h3>O Básico<span class="hx:absolute hx:-mt-20" id="o-básico"></span>
    <a href="#o-b%c3%a1sico" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Você vai querer clonar do meu <a href="https://github.com/akitaonrails/pusher_lite_demo/tree/v0.1"target="_blank" rel="noopener">repositório de exemplo</a>, assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>git clone https://github.com/akitaonrails/pusher_lite_demo
cd pusher_lite_demo
git checkout tags/v0.1 -b v0.1
bundle</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Esta é uma implementação muito, muito simples de um chat em tempo real, baseado em websocket, usando Pusher. A ideia é a seguinte:</p>
<p>Começamos tendo um Form no front-end para enviar mensagens</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="c">&lt;!-- app/views/home/index.html.erb --&gt;</span>
</span></span><span class="line"><span class="cl"><span class="err">&lt;</span>%= form_for @event, url: events_path, remote: true, html: {class: &#34;pure-form pure-form-stacked&#34;} do |f| %&gt;
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">fieldset</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">legend</span><span class="p">&gt;</span>Send your message remotely<span class="p">&lt;/</span><span class="nt">legend</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="err">&lt;</span>%= f.text_field :name, placeholder: &#34;Name&#34; %&gt;
</span></span><span class="line"><span class="cl">    <span class="err">&lt;</span>%= f.text_field :message, placeholder: &#34;Message&#34; %&gt;
</span></span><span class="line"><span class="cl">    <span class="err">&lt;</span>%= f.submit &#34;Send message&#34;, class: &#34;pure-button pure-button-primary&#34; %&gt;
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">fieldset</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="err">&lt;</span>% end %&gt;</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Ele usa o suporte nativo a jQuery do Rails para fazer o post Ajax do form ao método &ldquo;EventsController#create&rdquo;:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># app/controllers/events_controller.rb</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">EventsController</span> <span class="o">&lt;</span> <span class="no">ApplicationController</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">create</span>
</span></span><span class="line"><span class="cl">    <span class="no">SendEventsJob</span><span class="o">.</span><span class="n">perform_later</span><span class="p">(</span><span class="n">event_params</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">event_params</span>
</span></span><span class="line"><span class="cl">    <span class="n">params</span><span class="o">.</span><span class="n">require</span><span class="p">(</span><span class="ss">:pusher_event</span><span class="p">)</span><span class="o">.</span><span class="n">permit</span><span class="p">(</span><span class="ss">:name</span><span class="p">,</span> <span class="ss">:message</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Só para anotar o processo, o &ldquo;routes.rb&rdquo; fica assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># config/routes.rb</span>
</span></span><span class="line"><span class="cl"><span class="no">Rails</span><span class="o">.</span><span class="n">application</span><span class="o">.</span><span class="n">routes</span><span class="o">.</span><span class="n">draw</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">resources</span> <span class="ss">:events</span><span class="p">,</span> <span class="ss">only</span><span class="p">:</span> <span class="o">[</span><span class="ss">:create</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">root</span> <span class="s1">&#39;home#index&#39;</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O layout HTML fica assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="o">&lt;!--</span> <span class="n">app</span><span class="o">/</span><span class="n">views</span><span class="o">/</span><span class="n">layout</span><span class="o">/</span><span class="n">application</span><span class="o">.</span><span class="n">html</span><span class="o">.</span><span class="n">erb</span> <span class="o">--&gt;</span>
</span></span><span class="line"><span class="cl"><span class="o">&lt;!</span><span class="no">DOCTYPE</span> <span class="n">html</span><span class="o">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="o">&lt;</span><span class="n">html</span><span class="o">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="o">&lt;</span><span class="n">head</span><span class="o">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="o">&lt;</span><span class="n">title</span><span class="o">&gt;</span><span class="no">Pusher</span> <span class="no">Lite</span> <span class="no">Demo</span><span class="o">&lt;</span><span class="sr">/title&gt;
</span></span></span><span class="line"><span class="cl"><span class="sr">  &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1&#34;&gt;
</span></span></span><span class="line"><span class="cl"><span class="sr">  &lt;meta name=&#34;pusher_key&#34; content=&#34;&lt;%= Rails.application.secrets.pusher_key %&gt;&#34;&gt;
</span></span></span><span class="line"><span class="cl"><span class="sr">  &lt;meta name=&#34;pusher_channel&#34; content=&#34;&lt;%= Rails.application.secrets.pusher_channel %&gt;&#34;&gt;
</span></span></span><span class="line"><span class="cl"><span class="sr">  &lt;%= stylesheet_link_tag    &#39;application&#39;, media: &#39;all&#39;, &#39;data-turbolinks-track&#39; =&gt; true %&gt;
</span></span></span><span class="line"><span class="cl"><span class="sr">  &lt;%= javascript_include_tag &#39;application&#39;, &#39;data-turbolinks-track&#39; =&gt; true %&gt;
</span></span></span><span class="line"><span class="cl"><span class="sr">  &lt;script src=&#34;https://akitaonrails.github.io/</span><span class="o">/</span><span class="n">js</span><span class="o">.</span><span class="n">pusher</span><span class="o">.</span><span class="n">com</span><span class="o">/</span><span class="mi">3</span><span class="o">.</span><span class="mi">0</span><span class="o">/</span><span class="n">pusher</span><span class="o">.</span><span class="n">min</span><span class="o">.</span><span class="n">js</span><span class="s2">&#34;&gt;&lt;/script&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">  &lt;%= csrf_meta_tags %&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">&lt;/head&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">&lt;body&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">&lt;div class=&#34;</span><span class="n">pure</span><span class="o">-</span><span class="n">menu</span> <span class="n">pure</span><span class="o">-</span><span class="n">menu</span><span class="o">-</span><span class="n">horizontal</span><span class="s2">&#34;&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">    Pusher Client Demo
</span></span></span><span class="line"><span class="cl"><span class="s2">...
</span></span></span><span class="line"><span class="cl"><span class="s2">&lt;/div&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">&lt;div class=&#34;</span><span class="n">pure</span><span class="o">-</span><span class="n">g</span><span class="o">-</span><span class="n">r</span><span class="s2">&#34;&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">  &lt;div class=&#34;</span><span class="n">pure</span><span class="o">-</span><span class="n">u</span><span class="o">-</span><span class="mi">1</span><span class="o">-</span><span class="mi">3</span> <span class="n">message</span><span class="o">-</span><span class="n">form</span><span class="s2">&#34;&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">    &lt;%= yield %&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">  &lt;/div&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">  
</span></span></span><span class="line"><span class="cl"><span class="s2">  &lt;div class=&#34;</span><span class="n">pure</span><span class="o">-</span><span class="n">u</span><span class="o">-</span><span class="mi">1</span><span class="o">-</span><span class="mi">3</span> <span class="n">message</span><span class="o">-</span><span class="n">receiver</span><span class="s2">&#34;&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">  &lt;/div&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">&lt;/div&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">&lt;/body&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">&lt;/html&gt;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Esse layout importa o &ldquo;application.js&rdquo; padrão, que configura o Pusher, estabelece a conexão Websocket e se inscreve para receber mensagens em um tópico específico com eventos específicos:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="c1">// app/assets/javascript/application.js
</span></span></span><span class="line"><span class="cl"><span class="c1">//= require jquery
</span></span></span><span class="line"><span class="cl"><span class="c1">//= require jquery_ujs
</span></span></span><span class="line"><span class="cl"><span class="c1">//= require turbolinks
</span></span></span><span class="line"><span class="cl"><span class="c1">//= require_tree .
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">$</span><span class="p">(</span><span class="nb">document</span><span class="p">).</span><span class="nx">on</span><span class="p">(</span><span class="s2">&#34;page:change&#34;</span><span class="p">,</span> <span class="kd">function</span><span class="p">(){</span>
</span></span><span class="line"><span class="cl">  <span class="kd">var</span> <span class="nx">pusherKey</span> <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="s2">&#34;meta[name=pusher_key]&#34;</span><span class="p">).</span><span class="nx">attr</span><span class="p">(</span><span class="s2">&#34;content&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="kd">var</span> <span class="nx">pusher</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Pusher</span><span class="p">(</span><span class="nx">pusherKey</span><span class="p">,</span> <span class="p">{</span> <span class="nx">encrypted</span><span class="o">:</span> <span class="kc">true</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kd">var</span> <span class="nx">pusherChannel</span> <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="s2">&#34;meta[name=pusher_channel]&#34;</span><span class="p">).</span><span class="nx">attr</span><span class="p">(</span><span class="s2">&#34;content&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="kd">var</span> <span class="nx">channel</span> <span class="o">=</span> <span class="nx">pusher</span><span class="p">.</span><span class="nx">subscribe</span><span class="p">(</span><span class="nx">pusherChannel</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl">  <span class="nx">channel</span><span class="p">.</span><span class="nx">bind</span><span class="p">(</span><span class="s1">&#39;new_message&#39;</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="nx">new_line</span> <span class="o">=</span> <span class="s2">&#34;&lt;p&gt;&lt;strong&gt;&#34;</span> <span class="o">+</span> <span class="nx">data</span><span class="p">.</span><span class="nx">name</span> <span class="o">+</span> <span class="s2">&#34;&lt;strong&gt;: &#34;</span> <span class="o">+</span> <span class="nx">data</span><span class="p">.</span><span class="nx">message</span> <span class="o">+</span> <span class="s2">&#34;&lt;/p&gt;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">$</span><span class="p">(</span><span class="s2">&#34;.message-receiver&#34;</span><span class="p">).</span><span class="nx">append</span><span class="p">(</span><span class="nx">new_line</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Ele pega os metadados de configuração das meta tags do layout, que por sua vez pegam os valores do &ldquo;config/secrets.yml&rdquo;:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">development</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">secret_key_base</span><span class="p">:</span><span class="w"> </span><span class="l">ded7c4a2a298c1b620e462b50c9ca6ccb60130e27968357e76cab73de9858f14556a26df885c8aa5004d0a7ca79c0438e618557275bdb28ba67a0ffb0c268056</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">pusher_url</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;%= ENV[&#39;PUSHER_URL&#39;] %&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">pusher_key</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;%= ENV[&#39;PUSHER_KEY&#39;] %&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">pusher_channel</span><span class="p">:</span><span class="w"> </span><span class="l">test_chat_channel</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">test</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">secret_key_base</span><span class="p">:</span><span class="w"> </span><span class="l">f51ff494801ff0f9e1711036ef6f2f6f1e13544b02326adc5629c6833ae90f1a476747fae94b792eba8a444305df8e7a5ad53f05ea4234692ac96cc44f372029</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">pusher_url</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;%= ENV[&#39;PUSHER_URL&#39;] %&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">pusher_key</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;%= ENV[&#39;PUSHER_KEY&#39;] %&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">pusher_channel</span><span class="p">:</span><span class="w"> </span><span class="l">test_chat_channel</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c"># Do not keep production secrets in the repository,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c"># instead read values from the environment.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">production</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">secret_key_base</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;%= ENV[&#34;SECRET_KEY_BASE&#34;] %&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">pusher_url</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;%= ENV[&#39;PUSHER_URL&#39;] %&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">pusher_key</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;%= ENV[&#39;PUSHER_KEY&#39;] %&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">pusher_channel</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;%= ENV[&#39;PUSHER_CHANNEL&#39;] %&gt;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E como estou usando dotenv-rails, o &ldquo;.env&rdquo; fica assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>PUSHER_URL: &#34;https://14e86e5fee3335fa88b0:2b94ff0f07ce9769567f@api.pusherapp.com/apps/159621&#34;
PUSHER_KEY: &#34;14e86e5fee3335fa88b0&#34;
PUSHER_CHANNEL: &#34;test_chat_channel&#34;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O Pusher é configurado no lado do servidor através deste initializer:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># config/initializers/pusher.rb</span>
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;pusher&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="no">Pusher</span><span class="o">.</span><span class="n">url</span> <span class="o">=</span> <span class="no">Rails</span><span class="o">.</span><span class="n">application</span><span class="o">.</span><span class="n">secrets</span><span class="o">.</span><span class="n">pusher_url</span>
</span></span><span class="line"><span class="cl"><span class="no">Pusher</span><span class="o">.</span><span class="n">logger</span> <span class="o">=</span> <span class="no">Rails</span><span class="o">.</span><span class="n">logger</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Por fim, o &ldquo;EventsController#create&rdquo; faz uma chamada assíncrona para um job do <a href="https://github.com/brandonhilkert/sucker_punch"target="_blank" rel="noopener">SuckerPunch</a>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">SendEventsJob</span> <span class="o">&lt;</span> <span class="no">ActiveJob</span><span class="o">::</span><span class="no">Base</span>
</span></span><span class="line"><span class="cl">  <span class="n">queue_as</span> <span class="ss">:default</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">perform</span><span class="p">(</span><span class="n">event_params</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="vi">@event</span> <span class="o">=</span> <span class="no">PusherEvent</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">event_params</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="vi">@event</span><span class="o">.</span><span class="n">save</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>A propósito, abrindo um parêntese, o <a href="https://github.com/brandonhilkert/sucker_punch"target="_blank" rel="noopener">SuckerPunch</a> é uma solução fantástica para tarefas assíncronas dentro do mesmo processo. É uma ótima opção para começar sem precisar montar um sistema separado com workers do Sidekiq.</p>
<p>Quando você tiver filas maiores ou jobs que demoram demais, aí sim parta para o Sidekiq. Se você usa ActiveJob, a transição é tão simples quanto mudar a seguinte linha de configuração no arquivo &ldquo;config/application.rb&rdquo;:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">config</span><span class="o">.</span><span class="n">active_job</span><span class="o">.</span><span class="n">queue_adapter</span> <span class="o">=</span> <span class="ss">:sucker_punch</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Esse job apenas chama o método &ldquo;save&rdquo; no fake-model &ldquo;PusherEvent&rdquo;:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">PusherEvent</span>
</span></span><span class="line"><span class="cl">  <span class="kp">include</span> <span class="no">ActiveModel</span><span class="o">::</span><span class="no">Model</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kp">attr_accessor</span> <span class="ss">:name</span><span class="p">,</span> <span class="ss">:message</span>
</span></span><span class="line"><span class="cl">  <span class="n">validates</span> <span class="ss">:name</span><span class="p">,</span> <span class="ss">:message</span><span class="p">,</span> <span class="ss">presence</span><span class="p">:</span> <span class="kp">true</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">save</span>
</span></span><span class="line"><span class="cl">    <span class="no">Pusher</span><span class="o">.</span><span class="n">trigger</span><span class="p">(</span><span class="no">Rails</span><span class="o">.</span><span class="n">application</span><span class="o">.</span><span class="n">secrets</span><span class="o">.</span><span class="n">pusher_channel</span><span class="p">,</span> <span class="s1">&#39;new_message&#39;</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nb">name</span><span class="p">:</span> <span class="nb">name</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="ss">message</span><span class="p">:</span> <span class="n">message</span>
</span></span><span class="line"><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Como é bem simples, o Gemfile também fica simples:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;pusher&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;dotenv-rails&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;purecss-rails&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;sucker_punch&#39;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Então o que ele faz é bem simples:</p>
<ol>
<li>A aplicação carrega o Pusher e a configuração necessária.</li>
<li>Quando o usuário acessa &ldquo;http://localhost:3000&rdquo; ele recebe o Form de mensagem.</li>
<li>Quando o usuário envia a mensagem, ela cai no &ldquo;EventsController#create&rdquo;, que chama o &ldquo;SendEventsJob&rdquo; do SuckerPunch, instancia um novo modelo &ldquo;PusherEvent&rdquo; com os params do form recebido e finalmente dispara a mensagem para o servidor do Pusher.</li>
<li>A mesma página do form também carrega o cliente javascript do Pusher e se conecta no tópico &ldquo;test_chat_channel&rdquo; e escuta o evento &ldquo;new_message&rdquo;, que é exatamente o que a chamada &ldquo;Pusher.trigger&rdquo; envia junto com os params da mensagem do form.</li>
<li>O servidor Pusher faz broadcast da mensagem recebida para todos os clientes Websocket inscritos.</li>
<li>O cliente javascript do Pusher no navegador do usuário recebe a nova mensagem e simplesmente formata e adiciona ao bloco HTML &ldquo;message-receiver&rdquo; na mesma página.</li>
</ol>
<p>O Pusher tem suporte para usuários autenticados, canais privados e mais coisas, mas isso aí já cobre 80% dos casos de uso. Você pode implementar isso como sistema de chat, sistema de notificação, ou qualquer coisa do tipo.</p>
<p>Sua aplicação Rails monta o HTML/Javascript do front-end para conectar no Pusher, escutando certos tópicos e eventos, e a mesma aplicação Rails dispara o Pusher no lado servidor, postando novas mensagens. O Pusher recebe as mensagens e faz o broadcast para os clientes inscritos nos seus tópicos. É isso.</p>
<h2>Ex Pusher Lite - Parte 1: Substituto inicial baseado em Phoenix<span class="hx:absolute hx:-mt-20" id="ex-pusher-lite---parte-1-substituto-inicial-baseado-em-phoenix"></span>
    <a href="#ex-pusher-lite---parte-1-substituto-inicial-baseado-em-phoenix" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A minha ideia original era fazer um drop-in replacement do servidor Pusher, usando o mesmo cliente Pusher, mas por enquanto isso não foi tão fácil de fazer.</p>
<p>Em vez disso, esta Parte 1 vai focar em implementar um servidor inicial ExPusherLite que também recebe eventos disparados pelo mesmo controller server-side do Rails e faz broadcast para o mesmo componente front-end do Rails através de WebSockets.</p>
<p>Eu segui o tutorial do <a href="https://labs.opendoor.com/phoenix-on-rails-for-client-push-notifications"target="_blank" rel="noopener">Daniel Neighman</a>. Tive que fazer alguns ajustes para conseguir rodar (e como ainda é só a Parte 1, ainda não é uma solução completa!)</p>
<p>Você pode clonar a versão inicial do <a href="https://github.com/akitaonrails/ex_pusher_lite"target="_blank" rel="noopener">meu outro repositório no Github</a> assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>git clone https://github.com/akitaonrails/ex_pusher_lite
cd ex_pusher_lite
mix deps.get</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O tutorial implementou um setup inicial do <a href="https://github.com/hassox/guardian"target="_blank" rel="noopener">Guardian</a> e do <a href="https://github.com/bryanjos/joken"target="_blank" rel="noopener">Joken</a> para JSON Web Tokens. Ainda estou me acostumando com a forma como os <a href="http://www.phoenixframework.org/docs/channels"target="_blank" rel="noopener">channels</a> são implementados no Phoenix.</p>
<p>Ele já vem pré-configurado com um único socket handler que faz multiplex das conexões. Você inicia através da aplicação OTP EndPoint:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># lib/ex_pusher_lite/endpoint.ex</span>
</span></span><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">ExPusherLite</span><span class="o">.</span><span class="n">Endpoint</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">Phoenix</span><span class="o">.</span><span class="n">Endpoint</span><span class="p">,</span> <span class="ss">otp_app</span><span class="p">:</span> <span class="ss">:ex_pusher_lite</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">socket</span> <span class="s2">&#34;/socket&#34;</span><span class="p">,</span> <span class="no">ExPusherLite</span><span class="o">.</span><span class="n">UserSocket</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Essa aplicação é iniciada pelo supervisor principal em &ldquo;lib/ex_pusher_lite.ex&rdquo;. Ele aponta o endpoint &ldquo;/socket&rdquo; para o socket handler &ldquo;UserSocket&rdquo;:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># web/channels/user_socket.ex</span>
</span></span><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">ExPusherLite</span><span class="o">.</span><span class="n">UserSocket</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">Phoenix</span><span class="o">.</span><span class="n">Socket</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">## Channels</span>
</span></span><span class="line"><span class="cl">  <span class="n">channel</span> <span class="s2">&#34;*&#34;</span><span class="p">,</span> <span class="no">ExPusherLite</span><span class="o">.</span><span class="n">RoomChannel</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">## Transports</span>
</span></span><span class="line"><span class="cl">  <span class="n">transport</span> <span class="ss">:websocket</span><span class="p">,</span> <span class="no">Phoenix</span><span class="o">.</span><span class="n">Transports</span><span class="o">.</span><span class="n">WebSocket</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># transport :longpoll, Phoenix.Transports.LongPoll</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>A função &ldquo;channel&rdquo; vem comentada, então comecei descomentando ela. Você pode fazer pattern match no nome do tópico tipo &ldquo;public:*&rdquo; para diferentes Channel handlers. Para esse teste inicial simples eu estou mandando tudo para o &ldquo;RoomChannel&rdquo;, que precisei criar:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">ExPusherLite</span><span class="o">.</span><span class="n">RoomChannel</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">Phoenix</span><span class="o">.</span><span class="n">Channel</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">Guardian</span><span class="o">.</span><span class="n">Channel</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># no auth is needed for public topics</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">join</span><span class="p">(</span><span class="s2">&#34;public:&#34;</span> <span class="o">&lt;&gt;</span> <span class="n">_topic_id</span><span class="p">,</span> <span class="n">_auth_msg</span><span class="p">,</span> <span class="n">socket</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">socket</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">join</span><span class="p">(</span><span class="n">topic</span><span class="p">,</span> <span class="sx">%{ claims: claims, resource: _resource }</span><span class="p">,</span> <span class="n">socket</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">permitted_topic?</span><span class="p">(</span><span class="n">claims</span><span class="o">[</span><span class="ss">:listen</span><span class="o">]</span><span class="p">,</span> <span class="n">topic</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span> <span class="ss">:ok</span><span class="p">,</span> <span class="sx">%{ message: &#34;Joined&#34; }</span><span class="p">,</span> <span class="n">socket</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span> <span class="ss">:error</span><span class="p">,</span> <span class="ss">:authentication_required</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">join</span><span class="p">(</span><span class="n">_room</span><span class="p">,</span> <span class="n">_payload</span><span class="p">,</span> <span class="n">_socket</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="ss">:error</span><span class="p">,</span> <span class="ss">:authentication_required</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">handle_in</span><span class="p">(</span><span class="s2">&#34;msg&#34;</span><span class="p">,</span> <span class="n">payload</span><span class="p">,</span> <span class="n">socket</span> <span class="o">=</span> <span class="sx">%{ topic: &#34;public:&#34; &lt;&gt; _ }</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">broadcast</span> <span class="n">socket</span><span class="p">,</span> <span class="s2">&#34;msg&#34;</span><span class="p">,</span> <span class="n">payload</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="ss">:noreply</span><span class="p">,</span> <span class="n">socket</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">handle_in</span><span class="p">(</span><span class="s2">&#34;msg&#34;</span><span class="p">,</span> <span class="n">payload</span><span class="p">,</span> <span class="n">socket</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">claims</span> <span class="o">=</span> <span class="no">Guardian</span><span class="o">.</span><span class="n">Channel</span><span class="o">.</span><span class="n">claims</span><span class="p">(</span><span class="n">socket</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">permitted_topic?</span><span class="p">(</span><span class="n">claims</span><span class="o">[</span><span class="ss">:publish</span><span class="o">]</span><span class="p">,</span> <span class="n">socket</span><span class="o">.</span><span class="n">topic</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="n">broadcast</span> <span class="n">socket</span><span class="p">,</span> <span class="s2">&#34;msg&#34;</span><span class="p">,</span> <span class="n">payload</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span> <span class="ss">:noreply</span><span class="p">,</span> <span class="n">socket</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span> <span class="ss">:reply</span><span class="p">,</span> <span class="ss">:error</span><span class="p">,</span> <span class="n">socket</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">permitted_topic?</span><span class="p">(</span><span class="kp">nil</span><span class="p">,</span> <span class="n">_</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="kp">false</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">permitted_topic?</span><span class="p">(</span><span class="o">[]</span><span class="p">,</span> <span class="n">_</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="kp">false</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">permitted_topic?</span><span class="p">(</span><span class="n">permitted_topics</span><span class="p">,</span> <span class="n">topic</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">matches</span> <span class="o">=</span> <span class="n">fn</span> <span class="n">permitted_topic</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="n">pattern</span> <span class="o">=</span> <span class="nb">String</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="n">permitted_topic</span><span class="p">,</span> <span class="s2">&#34;:*&#34;</span><span class="p">,</span> <span class="s2">&#34;:.*&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="no">Regex</span><span class="o">.</span><span class="n">match?</span><span class="p">(</span><span class="o">~</span><span class="n">r</span><span class="o">/</span><span class="p">\</span><span class="n">A</span><span class="c1">#{pattern}\z/, topic)</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">    <span class="no">Enum</span><span class="o">.</span><span class="n">any?</span><span class="p">(</span><span class="n">permitted_topics</span><span class="p">,</span> <span class="n">matches</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Isso tudo veio direto do tutorial original do Daniel. A parte importante para esse exemplo é a primeira função &ldquo;join&rdquo;. As outras lidam com permissões e autenticação que vêm através de uma claim JWT. Vou tratar disso na Parte 2.</p>
<p>Para fazer isso funcionar, tive que adicionar as dependências em &ldquo;mix.exs&rdquo;:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># mix.exs</span>
</span></span><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">ExPusherLite</span><span class="o">.</span><span class="n">Mixfile</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">Mix</span><span class="o">.</span><span class="n">Project</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="n">defp</span> <span class="n">deps</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="o">[</span><span class="p">{</span><span class="ss">:phoenix</span><span class="p">,</span> <span class="s2">&#34;~&gt; 1.0.3&#34;</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">     <span class="p">{</span><span class="ss">:phoenix_ecto</span><span class="p">,</span> <span class="s2">&#34;~&gt; 1.1&#34;</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">     <span class="p">{</span><span class="ss">:postgrex</span><span class="p">,</span> <span class="s2">&#34;&gt;= 0.0.0&#34;</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">     <span class="p">{</span><span class="ss">:phoenix_html</span><span class="p">,</span> <span class="s2">&#34;~&gt; 2.1&#34;</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">     <span class="p">{</span><span class="ss">:phoenix_live_reload</span><span class="p">,</span> <span class="s2">&#34;~&gt; 1.0&#34;</span><span class="p">,</span> <span class="ss">only</span><span class="p">:</span> <span class="ss">:dev</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">     <span class="p">{</span><span class="ss">:cowboy</span><span class="p">,</span> <span class="s2">&#34;~&gt; 1.0&#34;</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">     <span class="p">{</span><span class="ss">:joken</span><span class="p">,</span> <span class="s2">&#34;~&gt; 1.0.0&#34;</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">     <span class="p">{</span><span class="ss">:guardian</span><span class="p">,</span> <span class="s2">&#34;~&gt; 0.7.0&#34;</span><span class="p">}</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E adicionar a configuração em &ldquo;config.exs&rdquo;:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># config/config.exs</span>
</span></span><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="n">config</span> <span class="ss">:joken</span><span class="p">,</span> <span class="ss">config_module</span><span class="p">:</span> <span class="no">Guardian</span><span class="o">.</span><span class="n">JWT</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">config</span> <span class="ss">:guardian</span><span class="p">,</span> <span class="no">Guardian</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="ss">issuer</span><span class="p">:</span> <span class="s2">&#34;ExPusherLite&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="ss">ttl</span><span class="p">:</span> <span class="p">{</span> <span class="mi">30</span><span class="p">,</span> <span class="ss">:days</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="ss">verify_issuer</span><span class="p">:</span> <span class="kp">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="ss">serializer</span><span class="p">:</span> <span class="no">ExPusherLite</span><span class="o">.</span><span class="n">GuardianSerializer</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="ss">atoms</span><span class="p">:</span> <span class="o">[</span><span class="ss">:listen</span><span class="p">,</span> <span class="ss">:publish</span><span class="p">,</span> <span class="ss">:crews</span><span class="p">,</span> <span class="ss">:email</span><span class="p">,</span> <span class="ss">:name</span><span class="p">,</span> <span class="ss">:id</span><span class="o">]</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora preciso adicionar um endpoint HTTP POST normal, primeiro adicionando ele ao router:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># web/router.ex</span>
</span></span><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">ExPusherLite</span><span class="o">.</span><span class="n">Router</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">ExPusherLite</span><span class="o">.</span><span class="n">Web</span><span class="p">,</span> <span class="ss">:router</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">pipeline</span> <span class="ss">:browser</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">plug</span> <span class="ss">:accepts</span><span class="p">,</span> <span class="o">[</span><span class="s2">&#34;html&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">plug</span> <span class="ss">:fetch_session</span>
</span></span><span class="line"><span class="cl">    <span class="n">plug</span> <span class="ss">:fetch_flash</span>
</span></span><span class="line"><span class="cl">    <span class="c1">#plug :protect_from_forgery</span>
</span></span><span class="line"><span class="cl">    <span class="n">plug</span> <span class="ss">:put_secure_browser_headers</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="n">scope</span> <span class="s2">&#34;/&#34;</span><span class="p">,</span> <span class="no">ExPusherLite</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">pipe_through</span> <span class="ss">:browser</span> <span class="c1"># Use the default browser stack</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">get</span> <span class="s2">&#34;/&#34;</span><span class="p">,</span> <span class="no">PageController</span><span class="p">,</span> <span class="ss">:index</span>
</span></span><span class="line"><span class="cl">    <span class="n">post</span> <span class="s2">&#34;/events&#34;</span><span class="p">,</span> <span class="no">EventsController</span><span class="p">,</span> <span class="ss">:create</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Repare que eu desabilitei totalmente a verificação de CSRF token no pipeline porque eu não estou enviando de volta o CSRF token do Phoenix a partir do controller do Rails. Agora, o &ldquo;EventsController&rdquo; também é quase todo do tutorial do Daniel:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># web/controllers/events_controller.ex</span>
</span></span><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">ExPusherLite</span><span class="o">.</span><span class="n">EventsController</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">ExPusherLite</span><span class="o">.</span><span class="n">Web</span><span class="p">,</span> <span class="ss">:controller</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">plug</span> <span class="ss">:authenticate</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">create</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="n">params</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">topic</span> <span class="o">=</span> <span class="n">params</span><span class="o">[</span><span class="s2">&#34;topic&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">event</span> <span class="o">=</span> <span class="n">params</span><span class="o">[</span><span class="s2">&#34;event&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">message</span> <span class="o">=</span> <span class="p">(</span><span class="n">params</span><span class="o">[</span><span class="s2">&#34;payload&#34;</span><span class="o">]</span> <span class="o">||</span> <span class="s2">&#34;{}&#34;</span><span class="p">)</span> <span class="o">|&gt;</span> <span class="no">Poison</span><span class="o">.</span><span class="n">decode!</span>
</span></span><span class="line"><span class="cl">    <span class="no">ExPusherLite</span><span class="o">.</span><span class="n">Endpoint</span><span class="o">.</span><span class="n">broadcast!</span> <span class="n">topic</span><span class="p">,</span> <span class="n">event</span><span class="p">,</span> <span class="n">message</span>
</span></span><span class="line"><span class="cl">    <span class="n">json</span> <span class="n">conn</span><span class="p">,</span> <span class="sx">%{}</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">defp</span> <span class="n">authenticate</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="n">_</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">secret</span> <span class="o">=</span> <span class="no">Application</span><span class="o">.</span><span class="n">get_env</span><span class="p">(</span><span class="ss">:ex_pusher_lite</span><span class="p">,</span> <span class="ss">:authentication</span><span class="p">)</span><span class="o">[</span><span class="ss">:secret</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Basic &#34;</span> <span class="o">&lt;&gt;</span> <span class="n">auth_token</span> <span class="o">=</span> <span class="n">hd</span><span class="p">(</span><span class="n">get_req_header</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="s2">&#34;authorization&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="no">Plug</span><span class="o">.</span><span class="n">Crypto</span><span class="o">.</span><span class="n">secure_compare</span><span class="p">(</span><span class="n">auth_token</span><span class="p">,</span> <span class="no">Base</span><span class="o">.</span><span class="n">encode64</span><span class="p">(</span><span class="n">secret</span><span class="p">))</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="n">conn</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span>
</span></span><span class="line"><span class="cl">      <span class="n">conn</span> <span class="o">|&gt;</span> <span class="n">send_resp</span><span class="p">(</span><span class="mi">401</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">)</span> <span class="o">|&gt;</span> <span class="n">halt</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Tive que mudar a função authenticate um pouco porque ou eu não entendi a implementação do Daniel ou ela esperava algo diferente. Mas nesta versão eu só estou esperando uma simples Basic HTTP Authentication no header &ldquo;authorization&rdquo;, que é uma string com o formato &ldquo;Basic [base64 username:password]&rdquo;. Olha como estou fazendo pattern match na string, removendo o Base64 e fazendo o <a href="http://codahale.com/a-lesson-in-timing-attacks/"target="_blank" rel="noopener">&ldquo;secure compare&rdquo;</a> (uma comparação binária em tempo constante para evitar timing attacks, isso já vem embutido no Phoenix).</p>
<p>Essa é uma técnica simples de autenticação para o controller do Rails fazer POST do trigger da mensagem da mesma forma que na versão Pusher.</p>
<p>E é isso, é tudo o que precisa para esse substituto inicial baseado em Phoenix do Pusher.</p>
<h2>Ex Pusher Lite - Parte 2: Mudando a aplicação Rails<span class="hx:absolute hx:-mt-20" id="ex-pusher-lite---parte-2-mudando-a-aplicação-rails"></span>
    <a href="#ex-pusher-lite---parte-2-mudando-a-aplica%c3%a7%c3%a3o-rails" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Agora que temos uma aplicação Phoenix bem básica que podemos iniciar com &ldquo;mix phoenix.server&rdquo; e disponibilizar em &ldquo;localhost:4000&rdquo;, podemos começar a mudar a aplicação Rails.</p>
<p>Como falei no começo, meu desejo original era usar o mesmo cliente javascript do Pusher só mudando o endpoint, acontece que isso é mais difícil do que eu pensava, então vou começar removendo a seguinte linha do layout da aplicação:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;//js.pusher.com/3.0/pusher.min.js&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Podemos nos livrar da gem do Pusher no Gemfile e do initializer &ldquo;pusher.rb&rdquo; também.</p>
<p>Agora, um substituto para o &ldquo;pusher.min.js&rdquo; é o próprio &ldquo;phoenix.js&rdquo; do Phoenix, que vem junto em &ldquo;deps/phoenix/web/static/js/phoenix.js&rdquo;. O problema é que ele é um source javascript ES6 que o Phoenix passa pelo Brunch para ser transpilado de volta para ES5 em toda aplicação Phoenix.</p>
<p>Mas eu estou copiando esse arquivo direto para o repositório do Rails em &ldquo;app/assets/javascripts/phoenix.es6&rdquo;. Eu poderia ter convertido ele para ES5, mas resolvi seguir o caminho mais difícil e adicionar suporte ao Babel no Asset Pipeline do Rails usando o <a href="http://nandovieira.com/using-es6-with-asset-pipeline-on-ruby-on-rails"target="_blank" rel="noopener">tutorial muito útil do Nando</a> sobre o assunto.</p>
<p>A essência é assim: primeiro adicionamos as dependências no Gemfile:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># Use SCSS for stylesheets</span>
</span></span><span class="line"><span class="cl"><span class="c1">#gem &#39;sass-rails&#39;, &#39;~&gt; 5.0&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;sass-rails&#39;</span><span class="p">,</span> <span class="ss">github</span><span class="p">:</span> <span class="s1">&#39;rails/sass-rails&#39;</span><span class="p">,</span> <span class="ss">branch</span><span class="p">:</span> <span class="s1">&#39;master&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;sprockets-rails&#39;</span><span class="p">,</span> <span class="ss">github</span><span class="p">:</span> <span class="s1">&#39;rails/sprockets-rails&#39;</span><span class="p">,</span> <span class="ss">branch</span><span class="p">:</span> <span class="s1">&#39;master&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;sprockets&#39;</span><span class="p">,</span> <span class="ss">github</span><span class="p">:</span> <span class="s1">&#39;rails/sprockets&#39;</span><span class="p">,</span> <span class="ss">branch</span><span class="p">:</span> <span class="s1">&#39;master&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;babel-transpiler&#39;</span>
</span></span><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="n">source</span> <span class="s1">&#39;https://rails-assets.org&#39;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">gem</span> <span class="s1">&#39;rails-assets-almond&#39;</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O Babel precisa de alguma configuração:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># config/initializers/babel.rb</span>
</span></span><span class="line"><span class="cl"><span class="no">Rails</span><span class="o">.</span><span class="n">application</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">assets</span><span class="o">.</span><span class="n">configure</span> <span class="k">do</span> <span class="o">|</span><span class="n">env</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="n">babel</span> <span class="o">=</span> <span class="no">Sprockets</span><span class="o">::</span><span class="no">BabelProcessor</span><span class="o">.</span><span class="n">new</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;modules&#39;</span>    <span class="o">=&gt;</span> <span class="s1">&#39;amd&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;moduleIds&#39;</span>  <span class="o">=&gt;</span> <span class="kp">true</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">env</span><span class="o">.</span><span class="n">register_transformer</span> <span class="s1">&#39;application/ecmascript-6&#39;</span><span class="p">,</span> <span class="s1">&#39;application/javascript&#39;</span><span class="p">,</span> <span class="n">babel</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E por algum motivo eu tive que redeclarar manualmente o application.js e o application.css no initializer de assets:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># config/initializers/assets.rb</span>
</span></span><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="no">Rails</span><span class="o">.</span><span class="n">application</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">assets</span><span class="o">.</span><span class="n">precompile</span> <span class="o">+=</span> <span class="sx">%w( application.css application.js )</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Precisamos do Almond para conseguir importar o módulo Socket do pacote javascript do Phoenix. Agora, mudamos o &ldquo;application.js&rdquo;:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="c1">//= require almond
</span></span></span><span class="line"><span class="cl"><span class="c1">//= require jquery
</span></span></span><span class="line"><span class="cl"><span class="c1">//= require jquery_ujs
</span></span></span><span class="line"><span class="cl"><span class="c1">//= require turbolinks
</span></span></span><span class="line"><span class="cl"><span class="c1">//= require phoenix
</span></span></span><span class="line"><span class="cl"><span class="c1">//= require_tree .
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">require</span><span class="p">([</span><span class="s1">&#39;application/boot&#39;</span><span class="p">]);</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Ele precisa de um arquivo &ldquo;app/assets/javascripts/application/boot.es6&rdquo;, que vem direto do tutorial do Nando:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">$</span> <span class="nx">from</span> <span class="s1">&#39;jquery&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">runner</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// All scripts must live in app/assets/javascripts/application/pages/**/*.es6.
</span></span></span><span class="line"><span class="cl">  <span class="kd">var</span> <span class="nx">path</span> <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="s1">&#39;body&#39;</span><span class="p">).</span><span class="nx">data</span><span class="p">(</span><span class="s1">&#39;route&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Load script for this page.
</span></span></span><span class="line"><span class="cl">  <span class="c1">// We should use System.import, but it&#39;s not worth the trouble, so
</span></span></span><span class="line"><span class="cl">  <span class="c1">// let&#39;s use almond&#39;s require instead.
</span></span></span><span class="line"><span class="cl">  <span class="k">try</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">require</span><span class="p">([</span><span class="nx">path</span><span class="p">],</span> <span class="nx">onload</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="kc">true</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">handleError</span><span class="p">(</span><span class="nx">error</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">onload</span><span class="p">(</span><span class="nx">Page</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// Instantiate the page, passing &lt;body&gt; as the root element.
</span></span></span><span class="line"><span class="cl">  <span class="kd">var</span> <span class="nx">page</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Page</span><span class="p">(</span><span class="nx">$</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Set up page and run scripts for it.
</span></span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="nx">page</span><span class="p">.</span><span class="nx">setup</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">page</span><span class="p">.</span><span class="nx">setup</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">page</span><span class="p">.</span><span class="nx">run</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Handles exception.
</span></span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">handleError</span><span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="nx">error</span><span class="p">.</span><span class="nx">message</span><span class="p">.</span><span class="nx">match</span><span class="p">(</span><span class="sr">/undefined missing/</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">warn</span><span class="p">(</span><span class="s1">&#39;missing module:&#39;</span><span class="p">,</span> <span class="nx">error</span><span class="p">.</span><span class="nx">message</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="s1">&#39; &#39;</span><span class="p">).</span><span class="nx">pop</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">throw</span> <span class="nx">error</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">$</span><span class="p">(</span><span class="nb">window</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">.</span><span class="nx">ready</span><span class="p">(</span><span class="nx">runner</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s1">&#39;page:load&#39;</span><span class="p">,</span> <span class="nx">runner</span><span class="p">);</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E ele depende de atributos na tag body, então mudamos nosso template de layout:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="c">&lt;!-- app/views/layouts/application.html.erb --&gt;</span>
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">body</span> <span class="na">data-route</span><span class="o">=</span><span class="s">&#34;application/pages/&lt;%= controller.controller_name %&gt;/&lt;%= controller.action_name %&gt;&#34;</span><span class="p">&gt;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Não mencionei antes, mas eu também tenho um &ldquo;HomeController&rdquo; só para servir como root path para a página HTML principal, ele tem um único método &ldquo;index&rdquo; e o template &ldquo;index.html.erb&rdquo; com o form de mensagem. Então vou precisar de um &ldquo;application/pages/home/index.es6&rdquo; dentro do path &ldquo;app/assets/javascripts&rdquo;:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span><span class="nx">Socket</span><span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;phoenix&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="kr">class</span> <span class="nx">Index</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">constructor</span><span class="p">(</span><span class="nx">root</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">this</span><span class="p">.</span><span class="nx">root</span> <span class="o">=</span> <span class="nx">root</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">setup</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// add event listeners
</span></span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;-&gt; Setting up Pusher Lite socket&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">let</span> <span class="nx">guardianToken</span> <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="s2">&#34;meta[name=guardian-token]&#34;</span><span class="p">).</span><span class="nx">attr</span><span class="p">(</span><span class="s2">&#34;content&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="kd">let</span> <span class="nx">csrfToken</span>     <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="s2">&#34;meta[name=guardian-csrf]&#34;</span><span class="p">).</span><span class="nx">attr</span><span class="p">(</span><span class="s2">&#34;content&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">let</span> <span class="nx">pusherKey</span>     <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="s2">&#34;meta[name=pusher_key]&#34;</span><span class="p">).</span><span class="nx">attr</span><span class="p">(</span><span class="s2">&#34;content&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="kd">let</span> <span class="nx">pusherChannel</span> <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="s2">&#34;meta[name=pusher_channel]&#34;</span><span class="p">).</span><span class="nx">attr</span><span class="p">(</span><span class="s2">&#34;content&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">let</span> <span class="nx">socket</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Socket</span><span class="p">(</span><span class="s2">&#34;ws://localhost:4000/socket&#34;</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">params</span><span class="o">:</span> <span class="p">{</span> <span class="nx">guardian_token</span><span class="o">:</span> <span class="nx">guardianToken</span><span class="p">,</span> <span class="nx">csrf_token</span><span class="o">:</span> <span class="nx">csrfToken</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="cl">    <span class="nx">socket</span><span class="p">.</span><span class="nx">connect</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Now that you are connected, you can join channels with a topic:
</span></span></span><span class="line"><span class="cl">    <span class="kd">let</span> <span class="nx">channel</span> <span class="o">=</span> <span class="nx">socket</span><span class="p">.</span><span class="nx">channel</span><span class="p">(</span><span class="nx">pusherChannel</span><span class="p">,</span> <span class="p">{})</span>
</span></span><span class="line"><span class="cl">    <span class="nx">channel</span><span class="p">.</span><span class="nx">join</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">      <span class="p">.</span><span class="nx">receive</span><span class="p">(</span><span class="s2">&#34;ok&#34;</span><span class="p">,</span> <span class="nx">resp</span> <span class="p">=&gt;</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">&#34;Joined successfully&#34;</span><span class="p">,</span> <span class="nx">resp</span><span class="p">)</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl">      <span class="p">.</span><span class="nx">receive</span><span class="p">(</span><span class="s2">&#34;error&#34;</span><span class="p">,</span> <span class="nx">resp</span> <span class="p">=&gt;</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">&#34;Unable to join&#34;</span><span class="p">,</span> <span class="nx">resp</span><span class="p">)</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nx">channel</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s2">&#34;msg&#34;</span><span class="p">,</span> <span class="nx">data</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="kd">let</span> <span class="nx">new_line</span> <span class="o">=</span> <span class="sb">`&lt;p&gt;&lt;strong&gt;</span><span class="si">${</span><span class="nx">data</span><span class="p">.</span><span class="nx">name</span><span class="si">}</span><span class="sb">&lt;strong&gt;: </span><span class="si">${</span><span class="nx">data</span><span class="p">.</span><span class="nx">message</span><span class="si">}</span><span class="sb">&lt;/p&gt;`</span>
</span></span><span class="line"><span class="cl">      <span class="nx">$</span><span class="p">(</span><span class="s2">&#34;.message-receiver&#34;</span><span class="p">).</span><span class="nx">append</span><span class="p">(</span><span class="nx">new_line</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">run</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// trigger initial action (e.g. perform http requests)
</span></span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;-&gt; perform initial actions&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Esse trecho é parecido com o tratamento do javascript do Pusher, mas estamos pegando um pouco mais de informação das meta tags, os tokens &ldquo;guardian-token&rdquo; e &ldquo;guardian-csrf&rdquo;. Como eu estava seguindo o tutorial do Daniel, também mudei o nome do evento de &ldquo;new_message&rdquo; para apenas &ldquo;msg&rdquo; e os tópicos agora precisam ter um prefixo &ldquo;public:&rdquo; para que o handler RoomChannel do Phoenix consiga fazer match no nome do tópico público corretamente.</p>
<p>Cada coisa em seu tempo. Para esse novo javascript ter os tokens corretos eu tive que adicionar o seguinte helper no layout das views:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">  <span class="err">&lt;</span>%= csrf_meta_tags %&gt;
</span></span><span class="line"><span class="cl">  <span class="err">&lt;</span>%= guardian_token_tags %&gt;
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">head</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">...</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E esse &ldquo;guardian_token_tags&rdquo; também vem direto do tutorial do Daniel:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">module</span> <span class="nn">GuardianHelper</span>
</span></span><span class="line"><span class="cl">  <span class="no">ISSUER</span> <span class="o">=</span> <span class="s2">&#34;pl-web-</span><span class="si">#{</span><span class="no">Rails</span><span class="o">.</span><span class="n">env</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="no">DIGEST</span> <span class="o">=</span> <span class="no">OpenSSL</span><span class="o">::</span><span class="no">Digest</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s1">&#39;sha256&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">guardian_token_tags</span>
</span></span><span class="line"><span class="cl">    <span class="n">token</span> <span class="o">=</span> <span class="no">Base64</span><span class="o">.</span><span class="n">urlsafe_encode64</span><span class="p">(</span><span class="no">SecureRandom</span><span class="o">.</span><span class="n">random_bytes</span><span class="p">(</span><span class="mi">32</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="o">[</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;&lt;meta content=</span><span class="se">\&#34;</span><span class="si">#{</span><span class="n">jwt</span><span class="p">(</span><span class="n">token</span><span class="p">)</span><span class="si">}</span><span class="se">\&#34;</span><span class="s2"> name=</span><span class="se">\&#34;</span><span class="s2">guardian-csrf</span><span class="se">\&#34;</span><span class="s2"> /&gt;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;&lt;meta content=</span><span class="se">\&#34;</span><span class="si">#{</span><span class="n">token</span><span class="si">}</span><span class="se">\&#34;</span><span class="s2"> name=</span><span class="se">\&#34;</span><span class="s2">guardian-token</span><span class="se">\&#34;</span><span class="s2"> /&gt;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="o">].</span><span class="n">shuffle</span><span class="o">.</span><span class="n">join</span><span class="o">.</span><span class="n">html_safe</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kp">private</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">jwt</span><span class="p">(</span><span class="n">token</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="no">JWT</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="n">jwt_claims</span><span class="p">(</span><span class="n">token</span><span class="p">),</span> <span class="no">Rails</span><span class="o">.</span><span class="n">application</span><span class="o">.</span><span class="n">secrets</span><span class="o">.</span><span class="n">pusher_key</span><span class="p">,</span> <span class="s1">&#39;HS256&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">jwt_claims</span><span class="p">(</span><span class="n">token</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="ss">aud</span><span class="p">:</span> <span class="ss">:csrf</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nb">sub</span><span class="p">:</span> <span class="n">jwt_sub</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="ss">iss</span><span class="p">:</span> <span class="no">ISSUER</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="ss">iat</span><span class="p">:</span> <span class="no">Time</span><span class="o">.</span><span class="n">now</span><span class="o">.</span><span class="n">utc</span><span class="o">.</span><span class="n">to_i</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="ss">exp</span><span class="p">:</span> <span class="p">(</span><span class="no">Time</span><span class="o">.</span><span class="n">now</span> <span class="o">+</span> <span class="mi">30</span><span class="o">.</span><span class="n">days</span><span class="p">)</span><span class="o">.</span><span class="n">utc</span><span class="o">.</span><span class="n">to_i</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="ss">s_csrf</span><span class="p">:</span> <span class="n">guardian_signed_token</span><span class="p">(</span><span class="n">token</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="ss">listen</span><span class="p">:</span> <span class="n">jwt_listens</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="ss">publish</span><span class="p">:</span> <span class="n">jwt_publish</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">jwt_sub</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">{}</span> <span class="k">unless</span> <span class="n">current_human</span><span class="o">.</span><span class="n">present?</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nb">id</span><span class="p">:</span> <span class="n">current_human</span><span class="o">.</span><span class="n">id</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nb">name</span><span class="p">:</span> <span class="n">current_human</span><span class="o">.</span><span class="n">full_name</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="ss">email</span><span class="p">:</span> <span class="n">current_human</span><span class="o">.</span><span class="n">email</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="ss">crews</span><span class="p">:</span> <span class="n">current_human</span><span class="o">.</span><span class="n">crews</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="ss">:identifier</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">jwt_listens</span>
</span></span><span class="line"><span class="cl">    <span class="n">listens</span> <span class="o">=</span> <span class="o">[</span><span class="s1">&#39;deploys:web&#39;</span><span class="p">,</span> <span class="s1">&#39;public:*&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">listens</span><span class="o">.</span><span class="n">push</span><span class="p">(</span><span class="s1">&#39;private:*&#39;</span><span class="p">)</span> <span class="k">if</span> <span class="n">current_human</span><span class="o">.</span><span class="n">try</span><span class="p">(</span><span class="ss">:in_crew?</span><span class="p">,</span> <span class="ss">:admins</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">listens</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">jwt_publish</span>
</span></span><span class="line"><span class="cl">    <span class="n">publish</span> <span class="o">=</span> <span class="o">[</span><span class="s1">&#39;public:*&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">publish</span><span class="o">.</span><span class="n">push</span><span class="p">(</span><span class="s1">&#39;private:*&#39;</span><span class="p">)</span> <span class="k">if</span> <span class="n">current_human</span><span class="o">.</span><span class="n">try</span><span class="p">(</span><span class="ss">:in_crew?</span><span class="p">,</span> <span class="ss">:admins</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">publish</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">guardian_signed_token</span><span class="p">(</span><span class="n">token</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">key</span> <span class="o">=</span> <span class="no">Rails</span><span class="o">.</span><span class="n">application</span><span class="o">.</span><span class="n">secrets</span><span class="o">.</span><span class="n">pusher_key</span>
</span></span><span class="line"><span class="cl">    <span class="n">signed_token</span> <span class="o">=</span> <span class="no">OpenSSL</span><span class="o">::</span><span class="no">HMAC</span><span class="o">.</span><span class="n">digest</span><span class="p">(</span><span class="no">DIGEST</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="n">token</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="no">Base64</span><span class="o">.</span><span class="n">urlsafe_encode64</span><span class="p">(</span><span class="n">signed_token</span><span class="p">)</span><span class="o">.</span><span class="n">gsub</span><span class="p">(</span><span class="sr">/={1,}$/</span><span class="p">,</span> <span class="s1">&#39;&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Tive que ajustar um pouquinho, principalmente para pegar as chaves certas do arquivo &ldquo;secrets.yml&rdquo;, que agora fica assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">development</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">secret_key_base</span><span class="p">:</span><span class="w"> </span><span class="l">ded7c4a2a298c1b620e462b50c9ca6ccb60130e27968357e76cab73de9858f14556a26df885c8aa5004d0a7ca79c0438e618557275bdb28ba67a0ffb0c268056</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">pusher_url</span><span class="p">:</span><span class="w"> </span><span class="l">http://&lt;%= ENV[&#39;PUSHER_KEY&#39;] %&gt;:&lt;%= ENV[&#39;PUSHER_SECRET&#39;] %&gt;@&lt;%= ENV[&#39;PUSHER_URL&#39;] %&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">pusher_key</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;%= ENV[&#39;PUSHER_KEY&#39;] %&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">pusher_secret</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;%= ENV[&#39;PUSHER_SECRET&#39;] %&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">pusher_channel</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;public:test_chat_channel&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">test</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">secret_key_base</span><span class="p">:</span><span class="w"> </span><span class="l">f51ff494801ff0f9e1711036ef6f2f6f1e13544b02326adc5629c6833ae90f1a476747fae94b792eba8a444305df8e7a5ad53f05ea4234692ac96cc44f372029</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">pusher_url</span><span class="p">:</span><span class="w"> </span><span class="l">http://&lt;%= ENV[&#39;PUSHER_KEY&#39;] %&gt;:&lt;%= ENV[&#39;PUSHER_SECRET&#39;] %&gt;@&lt;%= ENV[&#39;PUSHER_URL&#39;] %&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">pusher_key</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;%= ENV[&#39;PUSHER_KEY&#39;] %&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">pusher_secret</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;%= ENV[&#39;PUSHER_SECRET&#39;] %&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">pusher_channel</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;public:test_chat_channel&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c"># Do not keep production secrets in the repository,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c"># instead read values from the environment.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">production</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">secret_key_base</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;%= ENV[&#34;SECRET_KEY_BASE&#34;] %&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">pusher_url</span><span class="p">:</span><span class="w"> </span><span class="l">http://&lt;%= ENV[&#39;PUSHER_KEY&#39;] %&gt;:&lt;%= ENV[&#39;PUSHER_SECRET&#39;] %&gt;@&lt;%= ENV[&#39;PUSHER_URL&#39;] %&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">pusher_key</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;%= ENV[&#39;PUSHER_KEY&#39;] %&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">pusher_secret</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;%= ENV[&#39;PUSHER_SECRET&#39;] %&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">pusher_channel</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;%= ENV[&#39;PUSHER_CHANNEL&#39;] %&gt;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Meu arquivo &ldquo;.env&rdquo; local fica assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>PUSHER_URL: &#34;localhost:4000&#34;
PUSHER_KEY: &#34;14e86e5fee3335fa88b0&#34;
PUSHER_SECRET: &#34;2b94ff0f07ce9769567f&#34;
PUSHER_CHANNEL: &#34;public:test_chat_channel&#34; </code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Esse trecho ainda precisa de mais trabalho, eu sei. Eu só copiei a key e a senha do Pusher como KEY e SECRET. Esse é o pedaço que eu mencionei que ajustei na função authenticate do RoomChannel no lado do Phoenix.</p>
<p>Agora que tenho isso no lugar, preciso mudar o modelo &ldquo;PusherEvent&rdquo; para disparar a mensagem do form para o EventsController do Phoenix, assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># app/models/event.rb</span>
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s2">&#34;net/http&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s2">&#34;uri&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">PusherEvent</span>
</span></span><span class="line"><span class="cl">  <span class="kp">include</span> <span class="no">ActiveModel</span><span class="o">::</span><span class="no">Model</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kp">attr_accessor</span> <span class="ss">:name</span><span class="p">,</span> <span class="ss">:message</span>
</span></span><span class="line"><span class="cl">  <span class="n">validates</span> <span class="ss">:name</span><span class="p">,</span> <span class="ss">:message</span><span class="p">,</span> <span class="ss">presence</span><span class="p">:</span> <span class="kp">true</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">save</span>
</span></span><span class="line"><span class="cl">    <span class="n">uri</span> <span class="o">=</span> <span class="no">URI</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="s2">&#34;</span><span class="si">#{</span><span class="no">Rails</span><span class="o">.</span><span class="n">application</span><span class="o">.</span><span class="n">secrets</span><span class="o">.</span><span class="n">pusher_url</span><span class="si">}</span><span class="s2">/events&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="no">Net</span><span class="o">::</span><span class="no">HTTP</span><span class="o">.</span><span class="n">post_form</span><span class="p">(</span><span class="n">uri</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;topic&#34;</span> <span class="o">=&gt;</span> <span class="no">Rails</span><span class="o">.</span><span class="n">application</span><span class="o">.</span><span class="n">secrets</span><span class="o">.</span><span class="n">pusher_channel</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;event&#34;</span> <span class="o">=&gt;</span> <span class="s2">&#34;msg&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;payload&#34;</span> <span class="o">=&gt;</span> <span class="p">{</span><span class="s2">&#34;name&#34;</span> <span class="o">=&gt;</span> <span class="nb">name</span><span class="p">,</span> <span class="s2">&#34;message&#34;</span> <span class="o">=&gt;</span> <span class="n">message</span><span class="p">}</span><span class="o">.</span><span class="n">to_json</span>
</span></span><span class="line"><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Como estou fazendo isso através do SuckerPunch, estou usando o bom e velho &ldquo;Net::HTTP.post()&rdquo; para postar a mensagem para o endpoint &ldquo;/events&rdquo; do Phoenix. O Phoenix vai autenticar corretamente porque o &ldquo;pusher_url&rdquo; está enviando o &ldquo;PUSHER_KEY:PUSHER_SECRET&rdquo; como HTTP Basic Auth. Isso vai parar no header &ldquo;authorization&rdquo; e o Phoenix vai autenticar corretamente o lado do servidor, depois ele vai fazer broadcast para as conexões WebSocket inscritas no tópico.</p>
<p>O novo javascript vai se inscrever no tópico &ldquo;public:test_chat_channel&rdquo; e escutar o evento &ldquo;msg&rdquo;. Quando receber o payload, ele formata a mensagem e, novamente, adiciona no mesmo lugar dentro da div &ldquo;message-receiver&rdquo;.</p>
<h3>Conclusão: Trabalho Futuro<span class="hx:absolute hx:-mt-20" id="conclusão-trabalho-futuro"></span>
    <a href="#conclus%c3%a3o-trabalho-futuro" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Então, com isso temos exatamente o mesmo comportamento da versão com Pusher, só que agora sob meu controle.</p>
<p>A ideia é o app Phoenix ter apps, uma autenticação real para diferentes apps. Aí toda aplicação Rails que eu fizer pode simplesmente conectar nesse mesmo serviço Phoenix.</p>
<p>Os próximos passos incluem implementar direito as partes de Guardian/JWT, depois posso pular para o suporte a canais privados e adicionar APIs HTTP para listar canais nos apps e usuários online nos canais.</p>
<p>Aí vou criar um segundo app Rails companion como dashboard de administração para consumir essas APIs e poder criar ou revogar apps e fazer manutenção e relatórios básicos. Isso deve ser um substituto bom o suficiente para uma solução de mensageria estilo Pusher que seja realmente rápida.</p>
]]></content:encoded><category>elixir-e-erlang</category><category>rails</category><category>sistemas-distribuidos</category></item><item><title>O Obrigatório "Por Que Elixir?" - Visão Pessoal</title><link>https://akitaonrails.github.io/2015/12/01/o-obrigatorio-por-que-elixir-visao-pessoal/</link><guid isPermaLink="true">https://akitaonrails.github.io/2015/12/01/o-obrigatorio-por-que-elixir-visao-pessoal/</guid><pubDate>Tue, 01 Dec 2015 13:26:00 -0200</pubDate><description>&lt;p&gt;Pois é, venho estudando e exercitando bastante com Elixir. O José Valim anunciou recentemente as novidades do futuro &lt;a href="https://twitter.com/josevalim/status/670595716776116224"target="_blank" rel="noopener"&gt;Elixir 1.2&lt;/a&gt;. O design da linguagem já é elegante, enxuto, e continua se polindo aos poucos.&lt;/p&gt;
&lt;p&gt;Antes de mais nada, sou Desenvolvedor de Aplicações Web. Lido com aplicações Ruby on Rails e infraestrutura. Portanto, não sou desenvolvedor Desktop, Mobile, de Games ou de Ferramentas. Isso é muito importante deixar claro logo de cara.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Pois é, venho estudando e exercitando bastante com Elixir. O José Valim anunciou recentemente as novidades do futuro <a href="https://twitter.com/josevalim/status/670595716776116224"target="_blank" rel="noopener">Elixir 1.2</a>. O design da linguagem já é elegante, enxuto, e continua se polindo aos poucos.</p>
<p>Antes de mais nada, sou Desenvolvedor de Aplicações Web. Lido com aplicações Ruby on Rails e infraestrutura. Portanto, não sou desenvolvedor Desktop, Mobile, de Games ou de Ferramentas. Isso é muito importante deixar claro logo de cara.</p>
<p>E, a propósito, conforme vou focando meus esforços de programação cada vez mais em Elixir, isso não significa que estou &ldquo;trocando&rdquo; de Ruby para Elixir. Não preciso fazer escolhas exclusivas, e na minha cabeça, ao menos por um período de tempo, Rails unindo forças com Phoenix vai ser um combo muito difícil de bater para minhas estratégias de desenvolvimento web.</p>
<p>Eu sei, Phoenix é construído como o Rails, então por que não trocar de vez: porque a maior parte dos sites baseados em conteúdo não precisa dos aspectos de concorrência do Phoenix, e eu falei &ldquo;por um período de tempo&rdquo; porque o Rails ainda tem um ecossistema gigantesco com gems mais maduras que tornam o desenvolvimento mais fácil e mais rápido. Isso pode mudar no futuro, mas por enquanto o combo faz sentido porque eu posso construir um site normal em Rails como faria normalmente (com Devise, ActiveAdmin, Spree ou o que for) e adicionar Phoenix para coisas como WebSockets (notificações em tempo real, chat em tempo real, background jobs que podem rodar de forma mais eficiente do que o Sidekiq, etc).</p>
<p>Esse artigo vai resumir minha visão pessoal em 2 frentes:</p>
<ul>
<li><a href="#developer-roles">Outras Funções de Desenvolvedor além da Web</a></li>
<li><a href="#functional-concepts">Conceitos &ldquo;Funcionais&rdquo; que Realmente Importam</a>
<ul>
<li><a href="#immutability">Imutabilidade e Passagem de Mensagens Opacas são MUITO importantes</a></li>
<li><a href="#coroutines">Coroutines e Schedulers</a></li>
<li><a href="#static-dynamic">Tipagem Estática vs Dinâmica ainda é controverso</a></li>
<li><a href="#fault-tolerance">Tolerância a Falhas: Não tenha medo do seu código</a></li>
</ul>
</li>
<li><a href="#summary">Resumo</a></li>
</ul>
<p>Como qualquer coisa sobre a qual eu queira argumentar, isso é <strong>longo</strong>, mas não seria legal apenas afirmar algo sem elaborar. Nessa busca por entendimento, eu posso ter confundido uma coisa ou outra, então me avisem na seção de comentários abaixo se houver coisas para corrigir ou explicar com mais detalhes.</p>
<p>Vamos começar.</p>
<p><a name="developer-roles"></a></p>
<h3>Outras Funções de Desenvolvedor além da Web<span class="hx:absolute hx:-mt-20" id="outras-funções-de-desenvolvedor-além-da-web"></span>
    <a href="#outras-fun%c3%a7%c3%b5es-de-desenvolvedor-al%c3%a9m-da-web" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Num ambiente Desktop, você definitivamente vai querer fazer uma combinação de <a href="https://github.com/nwjs/nw.js/"target="_blank" rel="noopener">Node-Webkit</a> com bibliotecas nativas. Se você está em ambientes corporativos específicos, não vai ter outras escolhas além do desenvolvimento .NET baseado em WFC ou Java Swing. Suas opções estão definidas há um bom tempo, e até o Visual Basic.NET ainda tem seu lugar. Toolchains específicas vão ser ditadas pela Microsoft e Oracle/Java Community Process.</p>
<p>Em ambientes Linux você ainda vai usar wrappers em torno de GTK+, Qt ou toolkits similares. Não há muitas saídas aqui.</p>
<p>Se você é um desenvolvedor Mobile-first, você <strong>precisa</strong> aprender o caminho do Swift e Objective-C (até certo ponto) para iOS e o sabor específico de Java para o Dalvik/ART do Android. Mas eu vou argumentar que você tem muito a ganhar no desenvolvimento nativo se usar <a href="http://www.rubymotion.com/"target="_blank" rel="noopener"><strong>RubyMotion</strong></a>. Ou, você pode simplesmente acompanhar o esforço do Facebook com <a href="https://facebook.github.io/react-native/"target="_blank" rel="noopener">React Native</a>. Há muita fragmentação nesse ambiente, você pode fazer o mínimo possível com Web Mobile e Phonegap/Cordova, e construir apps interessantes com ferramentas como o Ionic Framework. O único consenso é que se você realmente quer construir o que há de mais avançado da próxima geração, vai querer mergulhar fundo nos frameworks nativos que cada plataforma oferece.</p>
<p>Se você é Desenvolvedor de Games você quer estar o mais próximo possível do metal. É perfeitamente viável escrever jogos mobile jogáveis usando qualquer número de bibliotecas Javascript junto com tecnologias do HTML 5 como Canvas e WebGL. Mas para o blockbuster da próxima geração você vai usar engines maduras como Unity ou Unreal ou até construir a sua própria se estiver realmente investido nesse campo. Isso vai te forçar a saber programar em C/C++. Realmente depende de quão fundo você quer ir na toca do coelho.</p>
<p>Se você é Desenvolvedor de Ferramentas vai ter os benefícios tanto da geração antiga quanto da nova de linguagens. Se você está mais perto do Kernel do Linux vai precisar mesmo de C/C++ na bagagem. Mas para a nova geração de containers leves (LXC), Docker, você pode se beneficiar de <strong>Go</strong>, um ambiente de desenvolvimento de aplicações talhado para tornar a vida mais fácil do que ter que lidar com as idiossincrasias de C/C++. <strong>Rust</strong> é outra excelente escolha nova para tornar mais fácil - entre outras coisas - escrever bibliotecas e ferramentas pequenas livres de vazamentos de memória (estou mencionando isso em particular porque é importante para linguagens como Ruby ou Python conseguirem adicionar performance fazendo binding com bibliotecas nativas em C, e o Rust facilita essa tarefa).</p>
<p>Linguagens diferentes têm times diferentes e objetivos de longo prazo diferentes, por isso comparar linguagens não é como comparar maçãs e laranjas. Go, por exemplo, é mais pesado que Rust, mas ambos são bons para ferramentas de linha de comando, daemons especializados, e no caso de Go, empreitadas pesadas em rede e concorrência.</p>
<p>Aos meus olhos, Go é um Java ou C++ &ldquo;melhor&rdquo;. E não interpretem mal: Java ainda é uma linguagem e plataforma muito boa. Há pouquíssimas coisas que chegam perto da maturidade da JVM e do extenso ecossistema por trás dela. Eu não pensaria nem por um segundo em tentar reescrever sistemas complexos escritos em Java como a biblioteca Lucene ou as soluções Elasticsearch/SOLR, por exemplo.</p>
<p>Mas o poder do Java padrão é difícil de ser desencadeado sem um aquecimento para o HotSpot pegar embalo. Isso faz dele uma solução ruim para ferramentas de linha de comando. Mas agora você tem um bom meio-termo com Go. Você também não tem uma boa experiência embarcando Java em outras plataformas, e aí você precisaria voltar para C, mas agora você tem outro bom meio-termo com Rust.</p>
<p>Se você quer desencadear modelos diferentes de programação, especialmente os mais adequados a abstrações concorrentes como o modelo de Atores tipo <a href="https://en.wikipedia.org/wiki/Actor_model_and_process_calculi_history"target="_blank" rel="noopener">CSP do Hoare</a>, você pode tentar <strong>Scala</strong> com <a href="http://akka.io/"target="_blank" rel="noopener">Akka</a> (que agora é a biblioteca padrão de atores) e o <a href="http://blog.paralleluniverse.co/2013/05/02/quasar-pulsar/"target="_blank" rel="noopener">Pulsar/Quasar</a> do <strong>Clojure</strong>. Akka e Quasar são os que chegam &ldquo;perto&rdquo; (mas nunca conseguem igualar) a plataforma OTP nativa do Erlang.</p>
<p>Para a Web no geral, você se vira muito bem com o ecossistema atual (em constante mudança, instável) Node.js, Python (Django, Plone), Ruby (Ruby on Rails, Sinatra), PHP (Zend, Laravel), até Perl tem seu lugar. Combinado com serviços maduros em diferentes plataformas (Elasticsearch em Java, PostgreSQL em C, RabbitMQ em Erlang), qualquer aplicação web grande e complexa pode ser escrita com qualquer combinação das melhores ferramentas que melhor atendam às suas necessidades.</p>
<p>Essa é uma visão injusta e curta, claro. Eu não cobri todos os aspectos da ciência da computação ou da indústria. Existem várias outras linguagens ativas e úteis como Lua, Haskell, Fortran, Ada, Julia, R. A mensagem é: você não precisa escolher uma única linguagem, vai realmente depender do que você vai entregar. E um verdadeiro artesão vai dominar muitas ferramentas para fazer o trabalho da melhor maneira possível.</p>
<p><a name="functional-concepts"></a></p>
<h3>Conceitos &ldquo;Funcionais&rdquo; que Realmente Importam<span class="hx:absolute hx:-mt-20" id="conceitos-funcionais-que-realmente-importam"></span>
    <a href="#conceitos-funcionais-que-realmente-importam" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><a href="http://www.akitaonrails.com/2015/10/28/personal-thoughts-on-the-current-functional-programming-bandwagon"target="_blank" rel="noopener">Eu escrevi sobre minhas opiniões</a> sobre a atual onda do chamado estilo Funcional de programação. Recomendo que leia antes de continuar.</p>
<p>Há alguns aspectos que são intransponíveis se você realmente quer ir além da pesquisa acadêmica e entrar no mundo real da produtividade.</p>
<p><a name="immutability"></a></p>
<h4>Imutabilidade e Passagem de Mensagens Opacas são MUITO importantes<span class="hx:absolute hx:-mt-20" id="imutabilidade-e-passagem-de-mensagens-opacas-são-muito-importantes"></span>
    <a href="#imutabilidade-e-passagem-de-mensagens-opacas-s%c3%a3o-muito-importantes" class="subheading-anchor" aria-label="Permalink for this section"></a></h4><p>Para que computação seja rápida, estamos acostumados a compartilhar dados entre rotinas. A gente movimenta ponteiros e altera dados no lugar.</p>
<p>Não é particularmente rápido tornar as coisas imutáveis e não compartilhadas. Quanto mais você torna os dados mutáveis e quanto mais compartilha, mais difícil fica fazer seu código rodar concorrentemente.</p>
<p>É um <strong>trade-off</strong> importante: se você vê dados mutáveis e estado compartilhado, está otimizando para performance.</p>
<p>Não dá para dizer que uma linguagem é essencialmente boa ou ruim por ter dados mutáveis ou imutáveis. Mas, valha o que valer, minha opinião pessoal é que é mais difícil convencer usuários a seguir convenções como <em>&ldquo;compartilhe o mínimo possível, mute o mínimo possível&rdquo;</em>. A maioria nem vai saber sobre isso se não for built-in e forçado. Eu prefiro ter imutabilidade forçada por padrão.</p>
<p>Em Erlang, dados são <strong>imutáveis</strong>. Similar a Java, ele <a href="http://erlang.org/pipermail/erlang-questions/2013-March/072760.html"target="_blank" rel="noopener">passa valores por referência</a> em chamadas de rotinas (não está copiando valores entre chamadas, como muitos confundem).</p>
<p>E no caso da recursão ele otimiza através do <a href="http://learnyousomeerlang.com/recursion"target="_blank" rel="noopener">Tail Call Optimization</a> para deixar mais rápido. A propósito, essa é uma otimização que a JVM ainda não consegue fazer direito. Clojure precisa de chamadas especiais &lsquo;recur&rsquo; e &rsquo;trampoline&rsquo;, por exemplo. Scala consegue reescrever a recursão de cauda para um loop em tempo de compilação, com a anotação &lsquo;@tailrec&rsquo;. <a href="http://ferd.ca/erlang-s-tail-recursion-is-not-a-silver-bullet.html"target="_blank" rel="noopener">Erlang tem suas próprias armadilhas</a> também, então não é tão preto no branco no momento.</p>
<p>Em Erlang, como expliquei antes, você roda funções dentro de processos completamente isolados. Se a função recorre ou bloqueia, ela permanece isolada. Processos só conseguem se comunicar enviando mensagens (imutáveis e opacas) uns aos outros. As mensagens são enfileiradas em uma &ldquo;run queue&rdquo; ou &ldquo;mailbox&rdquo; e a função pode escolher receber e responder a essas mensagens. É isso.</p>
<p>Então, você pode passar valores por referência entre rotinas, ou pode compartilhar dados em um processo terceiro como mediador dos dados. Uma dessas infraestruturas embutidas no Erlang é o <strong>ETS</strong>, o <a href="http://elixir-lang.org/getting-started/mix-otp/ets.html"target="_blank" rel="noopener">Erlang Term Storage</a>, que é parte da chamada plataforma OTP. Pense no ETS como um armazenamento chave-valor embutido muito simples e muito rápido como o Memcached. Você usa ele para os mesmos casos de uso de um cache e é tão simples quanto fazer assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">table</span> <span class="o">=</span> <span class="ss">:ets</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="ss">:my_fancy_cache</span><span class="p">,</span> <span class="o">[</span><span class="ss">:set</span><span class="p">,</span> <span class="ss">:protected</span><span class="o">]</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="ss">:ets</span><span class="o">.</span><span class="n">insert</span><span class="p">(</span><span class="n">table</span><span class="p">,</span> <span class="p">{</span><span class="s2">&#34;some_key&#34;</span><span class="p">,</span> <span class="n">some_value</span><span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="ss">:ets</span><span class="o">.</span><span class="n">lookup</span><span class="p">(</span><span class="n">table</span><span class="p">,</span> <span class="s2">&#34;some_key&#34;</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Muitos podem argumentar que o <strong>isolamento rígido de processos</strong> do Erlang e a comunicação restrita estritamente à passagem de mensagens opacas é exagero e que dá para passar usando algo parecido com a <a href="http://clojure.org/refs"target="_blank" rel="noopener">MVCC Software Transaction Memory</a> do Clojure, ou STM. Você até tem STM no Erlang, com a outra ferramenta nativa do OTP, construída em cima do ETS, chamada <strong>Mnesia</strong>. Ela oferece o equivalente a transações ACID de banco de dados em memória. Não é um conceito novo, mas STM não está disponível como funcionalidade de linguagem e ainda é incerto se realmente é uma boa escolha tê-lo.</p>
<p>Um resultado inspirado, eu acredito, da escolha do Clojure de ter memória transacional com fila de histórico e isolamento de snapshots é mostrado em sua joia da coroa, <a href="http://www.datomic.com/"target="_blank" rel="noopener">Datomic</a>. A ideia não é revolucionária de jeito nenhum, já que você tem muitos outros <a href="http://www.xaprb.com/blog/2013/12/28/immutability-mvcc-and-garbage-collection/"target="_blank" rel="noopener">trabalhos prévios</a> como RethinkDB, CouchDB, e extensões para bancos de dados existentes. Bom para um serviço, ainda assim eu não acho que seja uma boa coisa compartilhar estado, mesmo que você tenha um transactor em volta desse estado. A imutabilidade do Erlang com isolamento rígido de processos ainda não tem páreo.</p>
<p><a name="coroutines"></a></p>
<h4>Coroutines e Schedulers<span class="hx:absolute hx:-mt-20" id="coroutines-e-schedulers"></span>
    <a href="#coroutines-e-schedulers" class="subheading-anchor" aria-label="Permalink for this section"></a></h4><p>Você já conhece sub-rotinas, faz isso o tempo todo particionando grandes porções de código em funções ou métodos menores que se chamam. Você talvez já conheça um tipo especializado de Coroutines na forma de <a href="http://bit.ly/1lVuLFJ"target="_blank" rel="noopener">Fibers</a> (como implementado pela primeira vez no Windows por volta de 1997).</p>
<p>Fibers oferecem um jeito da execução da sua função atual &ldquo;ceder&rdquo; (yield) de volta ao seu chamador, preservando seu estado atual, e então o chamador pode &ldquo;retomar&rdquo; (resume) a Fiber suspensa para continuar sua execução de onde parou da última vez. Isso permite multitasking cooperativo, não preemptivo. Temos Fibers em Python, Ruby e outras linguagens e isso permite a criação de construções como Generators. Até Javascript pode ter algum tipo de Fibers se você adicionar bibliotecas como <a href="https://github.com/laverdet/node-fibers"target="_blank" rel="noopener">node-fibers</a>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">Fiber</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;fibers&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">sleep</span><span class="p">(</span><span class="nx">ms</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="nx">fiber</span> <span class="o">=</span> <span class="nx">Fiber</span><span class="p">.</span><span class="nx">current</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">setTimeout</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">fiber</span><span class="p">.</span><span class="nx">run</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span> <span class="nx">ms</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">Fiber</span><span class="p">.</span><span class="k">yield</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>A chamada para &lsquo;yield&rsquo; suspende a execução atual até que a função no &lsquo;setTimeout&rsquo; seja chamada. Aí ela chama &lsquo;run&rsquo;, que retoma a função previamente cedida. Isso ainda é &ldquo;rudimentar&rdquo; comparado a coroutines: porque a própria função tem que ceder controle para o reactor event loop, no caso de uma aplicação Node.js. Se você não fizer isso, vai bloquear o event loop num processo Node.js single threaded, e portanto bloqueia tudo até a função terminar, derrotando todo o propósito. E essa é uma daquelas &ldquo;convenções&rdquo; que &ldquo;bons&rdquo; programadores deveriam seguir, mas a maioria vai esquecer.</p>
<p>Fibers são úteis para tornar menos feio programar num ambiente Reactor rudimentar, onde você depende de callbacks chamando callbacks e acaba com o anti-padrão da <a href="https://bjouhier.wordpress.com/2012/03/11/fibers-and-threads-in-node-js-what-for/"target="_blank" rel="noopener">pirâmide da perdição de callbacks</a>. Com Fibers você pode programar como faria numa linguagem imperativa síncrona, transformando esse código Javascript feio:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">archiveOrders</span><span class="p">(</span><span class="nx">date</span><span class="p">,</span> <span class="nx">cb</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">db</span><span class="p">.</span><span class="nx">connect</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">err</span><span class="p">,</span> <span class="nx">conn</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="k">return</span> <span class="nx">cb</span><span class="p">(</span><span class="nx">err</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">conn</span><span class="p">.</span><span class="nx">query</span><span class="p">(</span><span class="s2">&#34;selectom orders where date &lt; ?&#34;</span><span class="p">,</span>  
</span></span><span class="line"><span class="cl">               <span class="p">[</span><span class="nx">date</span><span class="p">],</span> <span class="kd">function</span><span class="p">(</span><span class="nx">err</span><span class="p">,</span> <span class="nx">orders</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="k">if</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="k">return</span> <span class="nx">cb</span><span class="p">(</span><span class="nx">err</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">      <span class="nx">helper</span><span class="p">.</span><span class="nx">each</span><span class="p">(</span><span class="nx">orders</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">order</span><span class="p">,</span> <span class="nx">next</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">conn</span><span class="p">.</span><span class="nx">execute</span><span class="p">(</span><span class="s2">&#34;insert into archivedOrders ...&#34;</span><span class="p">,</span> 
</span></span><span class="line"><span class="cl">                     <span class="p">[</span><span class="nx">order</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span> <span class="p">...],</span> <span class="kd">function</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="k">if</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="k">return</span> <span class="nx">cb</span><span class="p">(</span><span class="nx">err</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">          <span class="nx">conn</span><span class="p">.</span><span class="nx">execute</span><span class="p">(</span><span class="s2">&#34;delete from orders where id=?&#34;</span><span class="p">,</span> 
</span></span><span class="line"><span class="cl">                       <span class="p">[</span><span class="nx">order</span><span class="p">.</span><span class="nx">id</span><span class="p">],</span> <span class="kd">function</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="k">return</span> <span class="nx">cb</span><span class="p">(</span><span class="nx">err</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="nx">next</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">          <span class="p">});</span>
</span></span><span class="line"><span class="cl">        <span class="p">});</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">&#34;orders been archived&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nx">cb</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">      <span class="p">});</span>
</span></span><span class="line"><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Nessa coisa mais gerenciável:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">archiveOrders</span> <span class="o">=</span> <span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">date</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kd">var</span> <span class="nx">conn</span> <span class="o">=</span> <span class="nx">db</span><span class="p">.</span><span class="nx">connect</span><span class="p">().</span><span class="nx">wait</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">  <span class="nx">conn</span><span class="p">.</span><span class="nx">query</span><span class="p">(</span><span class="s2">&#34;selectom orders where date &lt; ?&#34;</span><span class="p">,</span>  
</span></span><span class="line"><span class="cl">             <span class="p">[</span><span class="nx">date</span><span class="p">]).</span><span class="nx">wait</span><span class="p">().</span><span class="nx">forEach</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">order</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">conn</span><span class="p">.</span><span class="nx">execute</span><span class="p">(</span><span class="s2">&#34;insert into archivedOrders ...&#34;</span><span class="p">,</span> 
</span></span><span class="line"><span class="cl">                 <span class="p">[</span><span class="nx">order</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span> <span class="p">...]).</span><span class="nx">wait</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="nx">conn</span><span class="p">.</span><span class="nx">execute</span><span class="p">(</span><span class="s2">&#34;delete from orders where id=?&#34;</span><span class="p">,</span> 
</span></span><span class="line"><span class="cl">                 <span class="p">[</span><span class="nx">order</span><span class="p">.</span><span class="nx">id</span><span class="p">]).</span><span class="nx">wait</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl">  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">&#34;orders been archived&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}).</span><span class="nx">future</span><span class="p">();</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>A confusão toda das <a href="https://en.wikipedia.org/wiki/Futures_and_promises"target="_blank" rel="noopener">Promises, Futures</a> depende em parte de Fibers adequadas. Javascript, sendo um design muito pobre, não vem com nada built-in e daí a proliferação de implementações de Fibers, Deferreds, Promises, Futures que nunca conseguem alcançar nem consenso nem pessoas realmente usando elas em larga escala.</p>
<p>Então, Fibers são ok. Coroutines são melhores porque você tem múltiplos pontos de suspender uma função e mais. E ainda melhor, em Erlang você nem precisa pensar em loops Reactor rudimentares (sim, reactors são uma construção rudimentar para concorrência quando você não tem outra boa escolha): tem <strong>chamadas assíncronas transparentes</strong>. Tudo em Erlang é assíncrono e não-bloqueante, mas você não lida com pirâmides de callback porque tem algo melhor por baixo: o <strong>Scheduler</strong>.</p>
<p>A propósito, eu acho muito frustrante que <a href="http://stackoverflow.com/questions/18058164/is-golang-goroutine-a-coroutine"target="_blank" rel="noopener">as &ldquo;goroutines&rdquo; do Go <strong>não são</strong> &ldquo;coroutines&rdquo; propriamente ditas</a>.</p>
<p>Para cada processo Erlang com suporte SMP (multiprocessamento simétrico) vai existir <strong>uma</strong> thread real por núcleo de CPU disponível no seu sistema, e para cada thread vai existir um único Scheduler para gerenciar as green threads internas (processos) e a run-queue.</p>
<p>Como programador, eu não preciso &ldquo;lembrar&rdquo; de ceder controle de volta a um event loop passivo. O scheduler vai cuidar de balancear o tempo de computação para cada processo concorrente. Se um processo estiver demorando demais, o Scheduler pode escolher suspendê-lo e dar tempo para outras rotinas. Erlang define uma <a href="http://erlang.org/pipermail/erlang-questions/2001-April/003132.html"target="_blank" rel="noopener">&ldquo;redução&rdquo;</a> e que existem diferentes níveis de prioridade de funções. Se uma função leva mais que 2.000 reduções, o Scheduler pode escolher suspendê-la. Se você tem 8 núcleos de CPU, mas a computação nos processos não é pesada, a VM pode escolher usar apenas 1 ou 2 Schedulers e deixar os outros 6 ociosos para que o hardware possa desligar os núcleos para economizar energia (!!). Isso mesmo, Erlang até é Eco Friendly!</p>
<p>E precisamos repetir isso de novo: porque cada processo é <strong>rigidamente isolado</strong>, com dados imutáveis e sem estado compartilhado, fica mais fácil suspender um processo em execução. No caso da JVM isso geralmente é implementado levantando exceções checadas e fazendo todo mundo implementar alguma interface Suspendable. Pode ser feito usando essa <a href="http://www.matthiasmann.de/content/view/24/26/"target="_blank" rel="noopener">biblioteca de continuation Java de terceiros</a> onde você cede levantando uma Exception (!) Coisa nojenta.</p>
<p>Rust <a href="https://github.com/rustcc/coroutine-rs"target="_blank" rel="noopener">ainda está implementando</a> algo para coroutines também, mas ainda nada tão maduro. Mas de novo, coroutines é apenas parte da história, você precisaria de um sistema mais pesado com schedulers em userland para fazer sentido. Go é um candidato melhor para incorporar tal sistema em seu runtime, mas também fica devendo em conseguir implementar tudo isso. Há o <a href="http://www.jerf.org/iri/post/2930"target="_blank" rel="noopener">Suture</a>, uma tentativa de ter parte do OTP em Go, mas não dá para ser feito. Até Akka, o primeiro clone OTP mainstream para Scala, não consegue chegar perto por causa das limitações da JVM. <a href="http://blog.paralleluniverse.co/2013/05/02/quasar-pulsar/"target="_blank" rel="noopener">Clojure com Pulsar/Quasar</a>, chega <strong>mais perto</strong>, mas ainda não chegou lá.</p>
<p>Agora, o Scheduler do Erlang não só é capaz de suspender e retomar processos, mas também cuida da passagem de mensagens entre eles. Então cada Scheduler tem sua própria run-queue para enfileirar e despachar mensagens. De novo, porque os dados são imutáveis, você só precisa de alguma forma de locking quando você quer outro Scheduler (em outra thread real) para tomar conta de alguns processos a fim de balancear o processamento entre os núcleos. Erlang suporta SMP <a href="http://erlang.org/pipermail/erlang-questions/2008-September/038231.html"target="_blank" rel="noopener">desde o OTP R12B</a> (estamos no R18 agora, e ainda evoluindo).</p>
<p>A maioria das linguagens ainda depende do modelo preemptivo de threads reais do SO para fazer multitasking. E isso é pesado e lento por causa de toda a troca de contexto envolvida e toda a lógica de locking que a maioria dos programadores vai fazer errado (a melhor prática para concorrência é: não use threads, é provável que você vá errar). De novo, fazemos as suposições corretas primeiro: programadores não conseguem fazer multithreading direito, então deixe um Scheduler fazer isso, quando necessário, e evitando trocas de contexto lentas do SO o máximo possível. Green threads suspendíveis combinadas com um Scheduler em userland coordenando troca cooperativa é uma escolha muito mais rápida e segura.</p>
<p>Se você quer aprender mais sobre coroutines, esse <a href="http://www.inf.puc-rio.br/~roberto/docs/corosblp.pdf"target="_blank" rel="noopener">paper de Lua</a> sobre o assunto explica em mais detalhes o que eu acabei de elaborar:</p>
<blockquote>
Implementar uma aplicação multitasking com coroutines de Lua é direto. Tarefas concorrentes podem ser modeladas por coroutines de Lua. Quando uma nova tarefa é criada, ela é inserida em uma lista de tarefas vivas. Um simples dispatcher de tarefas pode ser implementado por um loop que continuamente itera sobre essa lista, retomando as tarefas vivas e removendo as que terminaram seu trabalho (essa condição pode ser sinalizada por um valor predefinido retornado pela função main da coroutine ao dispatcher). Eventuais problemas de fairness, que são fáceis de identificar, podem ser resolvidos adicionando pedidos de suspensão em tarefas demoradas.
</blockquote>
<p><a name="static-dynamic"></a></p>
<h4>Tipagem Estática vs Dinâmica ainda é controverso<span class="hx:absolute hx:-mt-20" id="tipagem-estática-vs-dinâmica-ainda-é-controverso"></span>
    <a href="#tipagem-est%c3%a1tica-vs-din%c3%a2mica-ainda-%c3%a9-controverso" class="subheading-anchor" aria-label="Permalink for this section"></a></h4><p>Vimos um movimento mais pesado de sistemas de tipagem estática burocráticos (principalmente Java antes do 6, C# antes do 4, C++) para linguagens puramente dinâmicas como Perl no fim dos anos 80, Python no fim dos 90, Ruby em meados de 2000. Tentamos sair do <em>&ldquo;deixar o compilador feliz&rdquo;</em> para o <em>&ldquo;deixar os programadores felizes&rdquo;</em>, o que faz mais sentido se você me perguntar.</p>
<p>Scala, Groovy, Haskell, Swift trouxeram um meio-termo bem prático com sistemas de <strong>Inferência de Tipos</strong> derivados de <a href="https://en.wikipedia.org/wiki/Hindley%E2%80%93Milner_type_system"target="_blank" rel="noopener">Hindley-Milner</a>, de um jeito que podemos codar meio que como em linguagens dinâmicas mas com o compilador fazendo mais trabalho para inferir tipos para nós antes de gerar o bytecode executável final.</p>
<p>Mas tem um <strong>grande</strong> porém: é muito difícil fazer <strong>hot swap</strong> de código dentro do runtime se você tem assinaturas estáticas. Eu não estou dizendo que é impossível, mas é muito mais difícil. Você até consegue recarregar código em Java (um exemplo é o <a href="https://github.com/spring-projects/spring-loaded"target="_blank" rel="noopener">Spring Loaded</a>) ou Haskell (existe um plugin de hotswap e outras alternativas). Você não faz reloads granulares numa linguagem tipada estaticamente porque se quiser mudar a assinatura de um método, precisa mudar o grafo que depende dessa assinatura. É factível, embora chato.</p>
<p>Em Erlang, porque não há essas dependências duras, e de novo por causa das vantagens inerentes de só ter dados imutáveis sem estado compartilhado, e dependências limitadas a passagem de mensagens opacas, você consegue recarregar granularmente um único módulo e mais importante de tudo: você consegue implementar callbacks simples para <strong>transformar o estado antigo de um processo em uma nova estrutura de estado</strong>, porque só recarregar o código é metade da história se você vai ter centenas de processos antigos reiniciando no novo código mas tendo que lidar com estado anterior. Num GenServer Erlang, você só implementa esse único callback:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">code_change</span><span class="p">(</span><span class="no">OldVersion</span><span class="p">,</span> <span class="no">CurrentState</span><span class="p">,</span> <span class="n">_Extra</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="p">{</span><span class="n">ok</span><span class="p">,</span> <span class="no">NewState</span><span class="p">}</span><span class="o">.</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Então, embora Inferência de Tipos seja um meio-termo legal, a flexibilidade da Tipagem Dinâmica vai além de ser fácil para programadores usarem. Como com Python, Ruby, Javascript, Perl e outras linguagens dinamicamente tipadas, você vai querer cobrir seu código com suítes de teste adequadas - o que não deveria ser opcional em linguagens fortemente tipadas de qualquer jeito. Não há dúvidas que a análise estática de um compilador ajuda muito, mas é minha visão pessoal que tipagem dinâmica me permite mais flexibilidade.</p>
<p><a name="fault-tolerance"></a></p>
<h3>Tolerância a Falhas: Não tenha medo do seu código<span class="hx:absolute hx:-mt-20" id="tolerância-a-falhas-não-tenha-medo-do-seu-código"></span>
    <a href="#toler%c3%a2ncia-a-falhas-n%c3%a3o-tenha-medo-do-seu-c%c3%b3digo" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Com Tipagem Dinâmica vamos acabar de novo na discussão sem fim de <em>&ldquo;programadores nunca fazem certo, precisamos de um compilador para forçar regras estáticas&rdquo;</em>. E você está quase certo: programadores erram, mas um compilador não vai te salvar de qualquer jeito, e pior: ele pode te dar uma falsa sensação de segurança. Não há buraco de segurança pior que falsa sensação de segurança. Uma suíte de testes é muito melhor para asseverar implementação adequada, e ainda assim não é garantia firme.</p>
<p>Eu disse que isso é controverso, já que um sistema de tipagem estática mais rígido é mais produtivo do que ter que testar unitariamente todos os tipos de input e output de toda função. Mas se você pensou isso, está pelo menos errado em que isso não é para o que servem os testes unitários: é para testar comportamentos de unidade, independente dos tipos. Testar tipos é o que chamamos de &ldquo;chicken typing&rdquo; e é outra forma de programação defensiva. Você deve testar comportamento, não coisas que um compilador checaria. E de novo, ter a tipagem estática trava minha flexibilidade porque agora eu tenho que lutar constantemente contra a tipagem, adicionar mais boilerplate, e em última análise se o comportamento está errado, o código está errado, apesar das checagens de tipo.</p>
<p>Para um sistema ser <strong>&ldquo;tolerante a código bugado ou falhas não capturadas&rdquo;</strong> é o oposto de poluir seu código com declarações de proteção como &ldquo;try/catch&rdquo; ou &ldquo;if err == x&rdquo; ou ter alguma forma de checagem em nível estático com Result monads (que a maioria das pessoas vai só desempacotar de qualquer jeito). Guards só vão até certo ponto. E sim, isso é anedótico já que não há estatísticas de que &ldquo;todo mundo vai desempacotar&rdquo; e a maioria dos bons programadores não vai, mas se a experiência me diz uma coisa é que programadores ruins vão cair no &ldquo;try/catch&rdquo; tudo quando eles não sabem o que fazer de qualquer maneira.</p>
<p>Você precisa de um sistema que permita que qualquer código bugado falhe e não derrube o ambiente em volta dele, o que o levaria a um estado inconsistente, corrompido.</p>
<p>O problema com código com defeito é que ele deixa o estado numa posição onde não tem para onde ir. E se esse estado for compartilhado, você deixa todo outro pedaço de código numa posição onde eles não conseguem decidir para onde ir em seguida. Você vai precisar desligar tudo e reiniciar a partir do último estado bom conhecido. Quase todos os fluxos de &ldquo;continuous delivery&rdquo; são implementados em torno de reiniciar tudo.</p>
<p>Em vez de ter que derrubar tudo caso você esqueça de fazer try/catch em alguma coisa, você pode confiar nos alicerces do Erlang de não compartilhar estado, ter dados imutáveis, e - o mais importante - ter o sistema de processos leves rigidamente isolados para fazer com que o processo que está segurando o código com defeito <strong>desligue e avise seu Supervisor</strong>. Essa é a ideia geral por trás do chamado OTP em Erlang.</p>
<p>A ideia de um Supervisor é ter um processo pequeno que monitora outros processos. O Supervisor tem o mínimo possível de código para raramente (ou nunca) falhar (o melhor tipo de código é &ldquo;nenhum código&rdquo;). Uma vez que um processo com defeito quebra por causa de exceção não capturada ou outras razões indeterminísticas, ele envia uma mensagem de notificação para o mailbox da run-queue do Supervisor, e então morre limpo. O Supervisor então escolhe o que fazer baseado em sua estratégia de restart subjacente.</p>
<p>Digamos que você tenha uma lista de URLs que está fazendo scraping. Mas você não antecipou estruturas sujas na sua lógica de parsing. O processo fazendo o scraping quebra e morre. O Supervisor é notificado e escolhe reiniciar o processo, dando ao novo processo o estado anterior - a lista de URLs - e agora que a URL com defeito não está mais ali, o novo processo pode continuar feliz o trabalho com a próxima URL da lista.</p>
<p>Esse é um exemplo simples da dinâmica entre a Erlang VM, o Supervisor e seus workers filhos. Você pode ir mais longe e ter várias Applications, que por sua vez sobem novos Supervisors, que por sua vez sobem processos filhos. E você tem uma Árvore de Supervisores que pode capturar exits e reiniciar bits granulares do seu código de runtime sem trazer o resto do sistema para um estado inconsistente.</p>
<p>Tal é a beleza do conceito de isolamento rígido de processos.</p>
<p>Toda I/O no sistema é embrulhada em <a href="http://www.erlang.org/doc/tutorial/c_port.html"target="_blank" rel="noopener">Ports</a>, que obedecem à lógica Async/callback transparentemente sem você ter que criar pirâmides de callbacks. O processo consumindo tais ports apenas é suspenso pelo Scheduler até que a chamada Async retorne e ele possa retomar o trabalho. Sem inferno de pirâmide de callback. Sem necessidade de implementações rudimentares de Fibers para permitir sistemas rudimentares de Promises/Futures. Apenas coroutines dentro de processos que podem ser suspensas e retomadas pelo Scheduler. Menos oportunidades para erros de programador se acumularem.</p>
<p>Então, um programador vai esquecer de codar todo possível ramo de execução e porque ele sabe disso, a <strong>pior coisa</strong> que pode acontecer não é se ele esquecer uma exceção não capturada, mas se ele decidir programar <strong>defensivamente</strong> e adicionar condições gerais para capturar <strong>qualquer</strong> erro e nunca levantá-lo. Você já viu antes, quando encontra código que está preso dentro de blocos try/catch genéricos, tentando evitar todo erro possível. Mas o que realmente acontece é que o sistema pode não quebrar, mas sua lógica e seu processamento estão com defeito no seu cerne. E você não vai descobrir, porque não quebra, portanto ninguém é nunca notificado! Você não vai acabar com menos bugs, vai acabar com uma montanha de bugs lógicos que nunca são percebidos porque são todos engolidos!!</p>
<p>Esse é o cerne da Tolerância a Falhas: não tenha medo do Erlang, tenha medo dos programadores! Em vez disso, devemos fazer o que Joe Armstrong apresentou em seu paper seminal <a href="http://www.erlang.org/download/armstrong_thesis_2003.pdf"target="_blank" rel="noopener">&ldquo;Making reliable distributed systems in the presence of software errors&rdquo;</a>. Esse é tanto um guia detalhado para Erlang e OTP quanto seus argumentos sobre como escrever sistemas tolerantes a falhas fazendo o programador escrever código tão claramente quanto ele originalmente pretendia, sem a necessidade de ser defensivo, com a confiança de que se ele esquecer alguma coisa, OTP vai estar lá para capturar e não deixar o sistema morrer, mas dar uma chance de consertar e recarregar sem disturbar outras partes boas do sistema.</p>
<p>Esse é o objetivo último da boa programação: não ser defensivo, não colocar try/catch em todo lugar porque você está com medo.</p>
<p><a name="summary"></a></p>
<h3>Resumo<span class="hx:absolute hx:-mt-20" id="resumo"></span>
    <a href="#resumo" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Esse curto resumo é a razão pela qual Erlang soa muito atraente para minhas necessidades de Desenvolvimento Web, ou para qualquer sistema complexo escalável, ao menos para mim.</p>
<p>Toda outra linguagem teve que fazer trade-offs. Toda nova linguagem em cima da JVM tem que lidar com limitações inerentes a como a JVM foi originalmente arquitetada. Rust ainda pode construir abstrações melhores, mas o escopo dele é para ferramentas e bibliotecas menores, não sistemas complexos distribuídos. Sim, eventualmente ele pode fazer o que C/C++ pode, e a Mozilla está realmente baseando o núcleo da próxima geração de seu browser core em Rust. Isso vai dar bootstrap a bibliotecas e frameworks de mais alto nível melhores, da mesma forma que a Apple usando Objective-C criou todo o conjunto de Core frameworks que tornam implementar aplicações complexas muito mais fácil.</p>
<p>Go fez uma escolha por manter a familiaridade com sua herança C++. Claro, ele tem toneladas de funcionalidades úteis, em particular as goroutines built-in que tornam o código concorrente muito mais fácil que em linguagens anteriores.</p>
<p>Haskell é muito estrito para a maioria dos programadores (sim, Monads, ainda difícil para o programador médio compreender plenamente) e apesar de opiniões contrárias, para mim ele ainda parece apelar mais a pesquisadores do que a desenvolvedores do dia-a-dia. Outras linguagens dinâmicas como Ruby, Python, podem eventualmente ir pelo caminho de Erlang com Ruby adicionando alguma imutabilidade (<a href="https://bugs.ruby-lang.org/issues/11473"target="_blank" rel="noopener">na 2.3</a> com String imutável por padrão), mas ainda há um longo caminho a percorrer.</p>
<p>Erlang tem tudo, como Joe Armstrong vislumbrou que um sistema tolerante a falhas deveria ser. Começou como um exercício implementado em Prolog em 1986. Migrou do antigo compilador JAM para a atual VM BEAM em 1998. Adicionou SMP por volta de 2008. Tem evoluído gradualmente, polindo suas arestas, sendo realmente testado em batalha em sistemas realmente de missão crítica por décadas. Está pronto para nós, agora mesmo.</p>
<p>Tem Tolerância a Falhas garantida pelos princípios de dados imutáveis, sem estado compartilhado, passagem pura de mensagens opacas, e processos suspendíveis, todos gerenciados por Schedulers. Isso garante que rotinas com defeito podem quebrar um único processo mas não o sistema inteiro e definitivamente não trazer o estado de outros processos a estados inconsistentes, corrompidos.</p>
<p>Aí você pode instrumentar sua máquina virtual e checar que Supervisors estão reiniciando filhos mais do que você quer e decidir consertar o código com bug. E uma vez que conserta, você pode escolher <strong>não</strong> desligar e reiniciar o sistema inteiro para recarregar as correções de código, você pode fazer isso granularmente, on the fly, com processos rodando que vão pegar as correções uma vez que o Supervisor os reinicie. O hot swap granular de código é garantido porque não há hierarquia de tipos com a qual se preocupar.</p>
<p>E porque você tem coroutines adequadas sem hierarquia de dependências de estado compartilhado, você pode ter <strong>exceções assíncronas</strong> que podem forçosamente desligar processos sem criar efeitos colaterais a outros processos rodando. Um Supervisor pode escolher reiniciar toda sua lista de filhos quando um de seus filhos quebra e outro de seus filhos dependia desse processo anterior. Você pode trocar de estratégia de restart &ldquo;one for one&rdquo; para &ldquo;one for all&rdquo; (como os Mosqueteiros).</p>
<p>Há apenas um problema com Erlang: ele não foi desenhado para &ldquo;felicidade do programador&rdquo;, um conceito que nos acostumamos a ter como certo por causa do Ruby e linguagens mais novas.</p>
<p>Erlang tem suas raízes em Prolog e isso aparece. Uma vez que você se aprofunda e realmente exercita com a linguagem você possivelmente consegue se acostumar com ele. Mas se você veio de linguagens dinâmicas mais modernas como Ruby, Python, Groovy, com certeza vai sentir falta das construções modernas confortáveis.</p>
<p>Elixir é a peça que faltava, a Pedra Filosofal se você quiser, que pode destrancar todos os 30 anos de refinamentos, maturidade, tecnologias industriais testadas em batalha em larga escala, para o programador médio.</p>
<p>Ele traz muitas construções modernas como tornar macros mais fáceis para permitir Domain Specific Languages, ter comentários testáveis no seu código, adicionar uma biblioteca padrão mais moderna que é facilmente reconhecível do ponto de vista de Ruby ou Clojure, polimorfismo via Protocols, e por aí vai.</p>
<p>Esse é um exemplo de Elixir direto dos testes do código fonte:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="no">Code</span><span class="o">.</span><span class="n">require_file</span> <span class="s2">&#34;../test_helper.exs&#34;</span><span class="p">,</span> <span class="n">__DIR__</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">Mix</span><span class="o">.</span><span class="n">ArchiveTest</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">MixTest</span><span class="o">.</span><span class="n">Case</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">doctest</span> <span class="no">Mix</span><span class="o">.</span><span class="n">Archive</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nb">test</span> <span class="s2">&#34;archive&#34;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">in_fixture</span> <span class="s2">&#34;archive&#34;</span><span class="p">,</span> <span class="n">fn</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="no">File</span><span class="o">.</span><span class="n">write</span> <span class="s2">&#34;.elixir&#34;</span><span class="p">,</span> <span class="s2">&#34;~&gt; 1.0.0&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="no">Mix</span><span class="o">.</span><span class="n">Archive</span><span class="o">.</span><span class="n">create</span><span class="p">(</span><span class="s2">&#34;.&#34;</span><span class="p">,</span> <span class="s2">&#34;sample.ez&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="n">archive</span> <span class="o">=</span> <span class="s1">&#39;sample.ez&#39;</span>
</span></span><span class="line"><span class="cl">      <span class="n">assert</span> <span class="no">File</span><span class="o">.</span><span class="n">exists?</span><span class="p">(</span><span class="n">archive</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="n">assert</span> <span class="n">has_zip_file?</span><span class="p">(</span><span class="n">archive</span><span class="p">,</span> <span class="s1">&#39;sample/.elixir&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="n">assert</span> <span class="n">has_zip_file?</span><span class="p">(</span><span class="n">archive</span><span class="p">,</span> <span class="s1">&#39;sample/priv/not_really_an.so&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="n">assert</span> <span class="n">has_zip_file?</span><span class="p">(</span><span class="n">archive</span><span class="p">,</span> <span class="s1">&#39;sample/ebin/Elixir.Mix.Tasks.Local.Sample.beam&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="n">assert</span> <span class="n">has_zip_file?</span><span class="p">(</span><span class="n">archive</span><span class="p">,</span> <span class="s1">&#39;sample/ebin/local_sample.app&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">defp</span> <span class="n">has_zip_file?</span><span class="p">(</span><span class="n">archive</span><span class="p">,</span> <span class="nb">name</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">files</span><span class="p">}</span> <span class="o">=</span> <span class="ss">:zip</span><span class="o">.</span><span class="n">list_dir</span><span class="p">(</span><span class="n">archive</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="no">Enum</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="n">files</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">match?</span><span class="p">({</span><span class="ss">:zip_file</span><span class="p">,</span> <span class="o">^</span><span class="nb">name</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">},</span> <span class="o">&amp;</span><span class="mi">1</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Código que pode ser escrito assim automaticamente me faz sorrir.</p>
<p>Essa é a combinação perfeita. De novo, não é sem deficiências. Erlang de jeito nenhum é uma linguagem rápida. É bem mais rápido que Ruby, Python, ou outras linguagens interpretadas. Pode ser feito um pouco mais rápido com compilação nativa através do compilador HIPE, mas ainda assim nem perto das velocidades de Go, Rust, ou qualquer derivado de Java como Scala, Groovy ou Clojure.</p>
<p>Então, se você realmente precisa de poder bruto de computação, vai querer Go ou Java. De novo, eu não estou dizendo que existem só esses 2, mas são as escolhas usuais se você não quer descer para C/C++. Haskell tem performance excelente, mas a curva de aprendizado dele está longe de ser estelar.</p>
<p>Erlang é um sistema inteiro, tem seu próprio sistema de scheduling, controla processos vivos, respirando, cada um com seu próprio garbage collector, controla captura de sinais do sistema e por aí vai. Foi desenhado para ser um servidor completo. É muito menor que um container Java Enterprise Edition completo, tão pequeno que você consegue empacotar ferramentas de linha de comando que sobem rápido o bastante. Mas esse não é o ponto forte. Para esse propósito você se sai melhor com Go ou até Rust.</p>
<p>Pelas mesmas razões ele não foi feito para ser uma linguagem embutível do jeito que Lua é. Não foi feito para criar bibliotecas que possam ser facilmente lincadas via FFI ou exports de função tipo C, do jeito que Rust pode.</p>
<p>Há jeitos de criar aplicações classe-desktop, especialmente com <a href="http://www.erlang.org/doc/apps/wx/chapter.html"target="_blank" rel="noopener">wxWidgets</a> cross-platform, do jeito que a aplicação built-in Observer de instrumentação é feita, mas Erlang não foi construído para ser um toolkit desktop.</p>
<p>Também porque ele prioriza correção, processos rigidamente isolados se comunicando só por mensagens opacas, estados imutáveis e não compartilhados, isso significa que Erlang não é adequado para processamento pesado de ciência de dados. Então duvido que seja a melhor escolha para analytics de Big Data, sequenciamento de DNA e outras coisas pesadas em que ferramentas como Julia, R, Fortran, são escolhas melhores. Não é a mesma coisa que dizer que ele não pode ser um bom núcleo de banco de dados, Riak e CouchDB já provaram isso. Mas queries complexas em cima de altos volumes de dados também não é o ponto forte.</p>
<p>Então, Erlang é bom para sistemas distribuídos, com alta concorrência de troca de mensagens opacas e proxying. O cenário exato em que a Web está. Aplicações Web com carga pesada de throughput que precisam de chats e notificações em tempo real, transações de pagamento pesadas e demoradas, coleta de dados de muitas fontes a fim de reduzi-los a respostas HTML ou JSON consumíveis.</p>
<p>Mas para o desenvolvedor Web médio (e por &ldquo;médio&rdquo; eu quero dizer minimamente capaz de arquitetar os tipos de sistemas complexos com que lidamos todo dia em desenvolvimento web, não construção simples de site estático), Erlang era um desafiante real, e agora podemos ter o conforto de uma linguagem moderna real com toques de Ruby e Clojure, sem as complexidades da tipagem forte mas com a segurança das construções built-in de Tolerância a Falhas para entregar aplicações Web modernas, altamente confiáveis e altamente escaláveis.</p>
]]></content:encoded><category>elixir-e-erlang</category><category>sistemas-distribuidos</category><category>arquitetura-de-software</category></item><item><title>"Yocto Services"! E o Meu Primeiro Mês com Elixir!</title><link>https://akitaonrails.github.io/2015/11/25/yocto-services-e-meu-primeiro-mes-com-elixir/</link><guid isPermaLink="true">https://akitaonrails.github.io/2015/11/25/yocto-services-e-meu-primeiro-mes-com-elixir/</guid><pubDate>Wed, 25 Nov 2015 18:04:00 -0200</pubDate><description>&lt;p&gt;Cara, no último mês (quase, de 27 de outubro a 25 de novembro) eu decidi que já passou da hora de mergulhar fundo e aprender Elixir de verdade. E foi exatamente o que eu fiz. Ainda sou iniciante, mas já me sinto bem confiante de que consigo encarar projetos em Elixir agora.&lt;/p&gt;
&lt;p&gt;O meu processo de aprendizado:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Li toda a &lt;a href="http://elixir-lang.org/getting-started/introduction.html"target="_blank" rel="noopener"&gt;Documentação oficial do Elixir&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;Li o livro &lt;a href="https://pragprog.com/book/elixir/programming-elixir"target="_blank" rel="noopener"&gt;Programming Elixir&lt;/a&gt; do Dave Thomas inteiro;&lt;/li&gt;
&lt;li&gt;Assisti a quase todos os screencasts do &lt;a href="http://elixirsips.com/"target="_blank" rel="noopener"&gt;Elixir Sips&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;Li o livro &lt;a href="https://www.manning.com/books/the-little-elixir-and-otp-guidebook"target="_blank" rel="noopener"&gt;The Little Elixir &amp;amp; OTP Guidebook&lt;/a&gt; do Benjamin Tan Wei Hao inteiro;&lt;/li&gt;
&lt;li&gt;Fiz uma porção de tutoriais e exercícios.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;E, finalmente, documentei tudo o que aprendi quase todo dia nos seguintes artigos:&lt;/p&gt;</description><content:encoded><![CDATA[<p>Cara, no último mês (quase, de 27 de outubro a 25 de novembro) eu decidi que já passou da hora de mergulhar fundo e aprender Elixir de verdade. E foi exatamente o que eu fiz. Ainda sou iniciante, mas já me sinto bem confiante de que consigo encarar projetos em Elixir agora.</p>
<p>O meu processo de aprendizado:</p>
<ol>
<li>Li toda a <a href="http://elixir-lang.org/getting-started/introduction.html"target="_blank" rel="noopener">Documentação oficial do Elixir</a>;</li>
<li>Li o livro <a href="https://pragprog.com/book/elixir/programming-elixir"target="_blank" rel="noopener">Programming Elixir</a> do Dave Thomas inteiro;</li>
<li>Assisti a quase todos os screencasts do <a href="http://elixirsips.com/"target="_blank" rel="noopener">Elixir Sips</a>;</li>
<li>Li o livro <a href="https://www.manning.com/books/the-little-elixir-and-otp-guidebook"target="_blank" rel="noopener">The Little Elixir &amp; OTP Guidebook</a> do Benjamin Tan Wei Hao inteiro;</li>
<li>Fiz uma porção de tutoriais e exercícios.</li>
</ol>
<p>E, finalmente, documentei tudo o que aprendi quase todo dia nos seguintes artigos:</p>
<ul>
<li><a href="http://www.akitaonrails.com/2015/10/27/how-fast-is-elixir-phoenix"target="_blank" rel="noopener">How Fast is Elixir/Phoenix?</a></li>
<li><a href="http://www.akitaonrails.com/2015/10/28/personal-thoughts-on-the-current-functional-programming-bandwagon"target="_blank" rel="noopener">Personal Thoughts on the Current Functional Programming Bandwagon</a></li>
<li><a href="http://www.akitaonrails.com/2015/10/29/phoenix-experiment-holding-2-million-websocket-clients"target="_blank" rel="noopener">Phoenix Experiment: Holding 2 Million Websocket clients!</a></li>
<li><a href="http://www.akitaonrails.com/2015/11/03/minha-primeira-semana-aprendendo-elixir"target="_blank" rel="noopener">My first week learning Elixir</a></li>
<li><a href="http://www.akitaonrails.com/2015/11/18/ex-manga-downloadr-um-exercicio-com-elixir"target="_blank" rel="noopener">Ex Manga Downloader, an exercise with Elixir</a></li>
<li><a href="http://www.akitaonrails.com/2015/11/19/ex-manga-downloadr-parte-2-poolboy-ao-resgate"target="_blank" rel="noopener">Ex Manga Downloadr - Part 2: Poolboy to the rescue!</a></li>
<li><a href="http://www.akitaonrails.com/2015/11/20/phoenix-15-minute-blog-comparacao-com-ruby-on-rails"target="_blank" rel="noopener">Phoenix &ldquo;15 Minute Blog&rdquo; comparison to Ruby on Rails</a></li>
<li><a href="http://www.akitaonrails.com/2015/11/22/observando-processos-em-elixir-the-little-elixir-otp-guidebook"target="_blank" rel="noopener">Observing Processes in Elixir - The Little Elixir &amp; OTP Guidebook</a></li>
<li><a href="http://www.akitaonrails.com/2015/11/25/exercicio-exmessenger-entendendo-nodes-em-elixir"target="_blank" rel="noopener">ExMessenger Exercise: Understanding Nodes in Elixir</a></li>
<li><a href="http://www.akitaonrails.com/2015/11/25/elixir-101-apresentando-a-sintaxe"target="_blank" rel="noopener">Elixir 101 - Introducing the Syntax</a></li>
</ul>
<p>Sim, eu sou prolífico e bastante focado. Estudei e escrevi tudo isso em menos de um mês (teria sido bem menos se eu pudesse ter usado as noites e finais de semana). Então eu diria que o desenvolvedor médio levaria pelo menos 3 a 4 meses para cobrir o mesmo material.</p>
<h3>Pensamento Inicial: Yocto Services!<span class="hx:absolute hx:-mt-20" id="pensamento-inicial-yocto-services"></span>
    <a href="#pensamento-inicial-yocto-services" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Para o bem ou para o mal, estamos no alvorecer da arquitetura de Micro Serviços. Resumidamente, é fragmentar o seu monolito em aplicações menores que respondem a endpoints HTTP - que as pessoas chamam de &ldquo;APIs&rdquo; - e que são responsáveis por um conjunto bem restrito de responsabilidades. Aí você cria uma aplicação web &ldquo;front-end&rdquo; que vai consumir esses serviços, como analytics, métodos de pagamento, diretórios de autenticação de usuários, e por aí vai.</p>
<p>Isso não é novidade nenhuma, claro. Ter APIs HTTP que devolvem estruturas JSON é só uma forma mais bonitinha de fazer os bons e velhos <strong>Remote Procedure Calls</strong>, ou RPCs, uma tecnologia que temos há décadas para interconectar clientes e servidores numa rede. Mas estou divagando.</p>
<p>Se isso é o que as pessoas chamam de serviços &ldquo;Micro&rdquo;, eu penso nos processos do Elixir como <strong>&ldquo;Yocto&rdquo;</strong> Services! (Milli &gt; Micro &gt; Nano &gt; Pico &gt; Femto &gt; Atto &gt; Zepto &gt; Yocto, aliás - eu posso ter acabado de inventar um novo termo aqui!)</p>
<p>Eu já descrevi um pouco da infraestrutura de Processos, como dar spawn neles, como trocar mensagens entre eles e como linkar eles. Então, vai lá ler <a href="http://www.akitaonrails.com/2015/11/22/observando-processos-em-elixir-the-little-elixir-otp-guidebook"target="_blank" rel="noopener">meu post anterior</a> se ainda não leu.</p>
<p>Dentro de uma aplicação Elixir você vai encontrar muitos processos, alguns da própria VM tocando o show e outros da sua própria aplicação. Se você arquitetou direito, implementou uma aplicação OTP, com grupos próprios de Supervisors e Children, todos organizados numa Supervision Tree. Um pequeno worker morre, o supervisor dele sabe como lidar com isso.</p>
<p>Agora, o modelo mental é o seguinte: pense num processo Elixir como um pequeno micro-serviço - um <strong>Yocto service</strong>, se preferir! - dentro da sua aplicação. Numa aplicação Phoenix, por exemplo, você não importa uma &ldquo;biblioteca de banco de dados&rdquo;, você na verdade dá start num &ldquo;Database Service&rdquo; (que é um Ecto Repo) que roda em paralelo com a aplicação Endpoint que responde aos requests HTTP que vêm da internet. O código nos seus controllers e models &ldquo;consome&rdquo; e envia mensagens para o &ldquo;Service&rdquo; Ecto Repo. É assim que você consegue visualizar o que está acontecendo.</p>
<p>Eu mostrei o Observer no artigo anterior também. Você vai encontrar uma árvore grande se abrir ele dentro do shell IEx de uma aplicação Phoenix:</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/531/big_phoenix-observer.png" alt="Observer Phoenix"  loading="lazy" /></p>
<p>Resumindo, você vai encontrar a seguinte seção na Supervision Tree do Phoenix App Pxblog:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Pxblog.Supervisor
- Pxblog.Endpoint
    &#43; Pxblog.Endpoint.Server
    &#43; Pxblog.PubSub.Supervisor
        * Pxblog.PubSub
        * Pxblog.PubSub.Local
- Pxblog.Repo
    &#43; Pxblog.Repo.Pool</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Esse é o Pxblog que eu expliquei no <a href="http://www.akitaonrails.com/2015/11/20/phoenix-15-minute-blog-comparacao-com-ruby-on-rails"target="_blank" rel="noopener">artigo de comparação entre Phoenix e Rails</a> que publiquei poucos dias atrás.</p>
<p>Eu ainda não li o código fonte do Phoenix, mas se eu estou interpretando o Observer corretamente, o Endpoint.Server controla um pool de processos que são listeners TCP que a aplicação está pronta para aceitar requests, concorrentemente, com overflow para aceitar mais conexões (acredito que seja uma implementação de pool tipo Poolboy, que eu expliquei na <a href="http://www.akitaonrails.com/2015/11/19/ex-manga-downloadr-parte-2-poolboy-ao-resgate"target="_blank" rel="noopener">Parte 2 do artigo do Ex Manga Downloader</a>).</p>
<p>Aí você tem as aplicações PubSub.Supervisor e PubSub.Local que eu acredito que dão suporte aos canais WebSocket.</p>
<p>Sozinho, o Repo controla 10 processos iniciais no pool dele, possivelmente um pool de conexões com o banco de dados. Repare como os grupos Endpoint e Repo estão em ramos paralelos da árvore de supervisão. Se o Repo falha por causa de algum problema externo do banco, o grupo do Endpoint não precisa falhar junto. Isso é o que está declarado na definição da Application do Pxblog em <tt>lib/pxblog.ex</tt>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>defmodule Pxblog do
  use Application

  # See http://elixir-lang.org/docs/stable/elixir/Application.html
  # for more information on OTP Applications
  def start(_type, _args) do
    import Supervisor.Spec, warn: false

    children = [
      # Start the endpoint when the application starts
      supervisor(Pxblog.Endpoint, []),
      # Start the Ecto repository
      worker(Pxblog.Repo, []),
      # Here you could define other workers and supervisors as children
      # worker(Pxblog.Worker, [arg1, arg2, arg3]),
    ]

    # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
    # for other strategies and supported options
    opts = [strategy: :one_for_one, name: Pxblog.Supervisor]
    Supervisor.start_link(children, opts)
  end
  ...
end</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Veja como ele define o Endpoint e o Repo debaixo do Pxblog.Supervisor.</p>
<p>Eu posso ir lá e matar à força o nó inteiro do Pxblog.Repo da Supervision Tree usando o Observer, como fiz no artigo anterior, e a estratégia certa entra em ação, o Phoenix Supervisor reinicia o Repo com sucesso, e ninguém vai perceber que algo quebrou por baixo dos panos.</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/532/big_phoenix_kill_repo.png" alt="Kill Repo"  loading="lazy" /></p>
<p>Do IEx eu ainda consigo fazer mais chamadas para o Repo desse jeito e ele responde como se nunca tivesse caído:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>iex(4)&gt; Pxblog.Repo.all(Pxblog.User)
[debug] SELECT u0.&#34;id&#34;, u0.&#34;username&#34;, u0.&#34;email&#34;, u0.&#34;password_digest&#34;, u0.&#34;inserted_at&#34;, u0.&#34;updated_at&#34; FROM &#34;users&#34; AS u0 [] OK query=78.2ms queue=3.2ms
[%Pxblog.User{__meta__: #Ecto.Schema.Metadata&lt;:loaded&gt;,
  email: &#34;akitaonrails@me.com&#34;, id: 1,
  inserted_at: #Ecto.DateTime&lt;2015-11-20T14:01:09Z&gt;, password: nil,
  password_confirmation: nil,
  password_digest: &#34;...&#34;,
  posts: #Ecto.Association.NotLoaded&lt;association :posts is not loaded&gt;,
  updated_at: #Ecto.DateTime&lt;2015-11-20T14:01:09Z&gt;, username: &#34;akitaonrails&#34;}]</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E a forma como eu penso sobre isso é assim: o meu shell IEx está mandando uma mensagem para o Yocto Service chamado Pxblog.Repo (na verdade ele encaminha mensagens para o adapter de banco de dados que aí faz o checkout de um processo do pool). Exatamente como eu consumiria Micro Serviços externos via APIs HTTP.</p>
<p>Então o panorama da sua aplicação é composto por uma série de processos e grupos de processos supervisionados, todos trabalhando para compor uma estrutura maior. Como eu disse em artigos anteriores, se um grupo de processos colapsar, o Supervisor dele entra em ação, captura o erro e usa a sua estratégia para, por exemplo, reiniciar todos os processos filhos, trazendo a aplicação de volta para um estado consistente, e sem você ter que reiniciar a aplicação Elixir inteira.</p>
<p>Então cada processo pode ser um Yocto Service completo, rodando online e esperando outros serviços consumirem ele, como os workers do Repo.</p>
<h3>Dividir e Conquistar<span class="hx:absolute hx:-mt-20" id="dividir-e-conquistar"></span>
    <a href="#dividir-e-conquistar" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>De novo, como disclaimer, ainda sou novo em Elixir, mas a forma que eu acho mais fácil de entender é assim:</p>
<ul>
<li>
<p>Se você precisa lidar com recursos externos, seja um arquivo, conexões de rede ou qualquer coisa fora da Erlang VM, você vai querer que isso seja um GenServer.</p>
</li>
<li>
<p>Aí, se você tem um GenServer, você quer dar start nele debaixo de um Supervisor (geralmente, o boilerplate simples que define os filhos e a estratégia de restart).</p>
</li>
<li>
<p>O número de processos GenServer que você quer iniciar depende de quantos processos paralelos você quer ter rodando. Por exemplo, se for um serviço de banco de dados, não tem sentido iniciar mais do que o número máximo de conexões disponíveis que o banco permite. Se você tem vários arquivos para processar, você quer no máximo o número de arquivos disponíveis ou - na prática - só alguns processos para lidar com lotes de arquivos. Geralmente você vai querer um pool de processos GenServer e, nesse caso, você quer usar Poolboy.</p>
</li>
<li>
<p>Um GenServer pode chamar outros GenServers. Você não quer usar mecanismo de tratamento de exceção do tipo try/catch porque o que você quer é justamente que aquele processo GenServer específico crashe se algo der errado: se o arquivo está corrompido, ou não existe, ou se a rede ficou instável ou caiu. O Supervisor vai substituir aquele processo por um GenServer novo no lugar dele e reabastecer o Pool se for preciso.</p>
</li>
<li>
<p>Você pode fazer GenServers <a href="http://www.akitaonrails.com/2015/11/25/exercicio-exmessenger-entendendo-nodes-em-elixir"target="_blank" rel="noopener">conversarem remotamente</a> usando o recurso de Node que eu expliquei 2 posts atrás, com o exemplo do ExMessenger. Aí seria como uma arquitetura normal de Micro Serviços, mas onde os Yocto Services internos é que estão na verdade fazendo a conversa.</p>
</li>
<li>
<p>Qualquer transformação sem efeitos colaterais (transformar uma entrada simples numa saída simples), tipo pegar uma string com o body de um HTML e fazer parse para uma Lista de tuplas, pode ser organizada num Module normal. Dá uma olhada em bibliotecas como <a href="https://github.com/philss/floki"target="_blank" rel="noopener">Floki</a> para ver como elas estão organizadas.</p>
</li>
</ul>
<p>Cada Erlang VM (chamada de BEAM) é um único processo do SO, que gerencia uma única thread real por core de CPU disponível na sua máquina. Cada thread real é gerenciada pelo seu próprio BEAM Scheduler, que vai fatiar tempo de processamento entre os processos leves lá dentro.</p>
<p>Cada processo BEAM tem a sua própria caixa de mensagens para receber mensagens (mais corretamente chamada de <strong>run-queue</strong>). Operações de I/O como gerenciamento de arquivos vão rodar de forma assíncrona e não vão bloquear o scheduler, que vai conseguir rodar outros processos enquanto espera o I/O.</p>
<p>Cada processo BEAM também tem o seu próprio heap separado e o seu próprio garbage collector (um copy collector geracional, de 2 estágios). Como cada processo tem muito pouco estado (variáveis dentro de uma função), cada parada do garbage collector é super curta e roda rápido.</p>
<p>Então, cada VM BEAM pode ser pensada como uma infraestrutura inteira de aplicação, com vários Yocto Services disponíveis para a sua aplicação chamar.</p>
<p>E como eu disse no <a href="http://www.akitaonrails.com/2015/11/25/exercicio-exmessenger-entendendo-nodes-em-elixir"target="_blank" rel="noopener">artigo anterior</a>, cada VM BEAM pode chamar remotamente outras VMs BEAM e trocar mensagens entre elas como se estivessem na mesma VM. As semânticas são quase as mesmas e você tem computação distribuída do jeito mais fácil.</p>
<p>A Erlang implementou um conjunto fantástico de primitivas que escalam rapidamente o quanto você quiser ou precisar, com as ligações certas para não te deixar na mão. E o Elixir conserta o único problema que a maioria das pessoas tem com Erlang: aquela <a href="http://www.akitaonrails.com/2015/11/25/elixir-101-apresentando-a-sintaxe"target="_blank" rel="noopener">sintaxe estranha inspirada no velho Prolog</a>. Elixir é uma camada fina e efetiva de design moderno de linguagem, em parte inspirada em Ruby (apesar de estar longe de ser um port, continua sendo Erlang por baixo).</p>
<p>Eu espero que essa série tenha ajudado a jogar alguma luz sobre por que Elixir é a melhor escolha entre a nova geração de linguagens preparadas para alta concorrência, e também espero ter deixado claro por que só alta concorrência não basta: você quer alta confiabilidade também. E nesse aspecto, a arquitetura OTP, embutida no Erlang, não tem competição.</p>
]]></content:encoded><category>elixir-e-erlang</category><category>sistemas-distribuidos</category><category>arquitetura-de-software</category></item><item><title>Exercício ExMessenger: Entendendo Nodes em Elixir</title><link>https://akitaonrails.github.io/2015/11/25/exercicio-exmessenger-entendendo-nodes-em-elixir/</link><guid isPermaLink="true">https://akitaonrails.github.io/2015/11/25/exercicio-exmessenger-entendendo-nodes-em-elixir/</guid><pubDate>Wed, 25 Nov 2015 13:19:00 -0200</pubDate><description>&lt;p&gt;Eu estava me exercitando seguindo este &lt;a href="http://drew.kerrigan.io/ditributed-elixir/"target="_blank" rel="noopener"&gt;post antigo de 2014&lt;/a&gt; do Drew Kerrigan, onde ele constrói um chat bem simplório, baseado em linha de comando, com um cliente que envia mensagens e comandos para um servidor.&lt;/p&gt;
&lt;p&gt;Isso é Elixir pré-1.0, e como é um exercício eu refatorei o código original e juntei os projetos do servidor (ex_messenger) e do cliente (ex_messenger_client) em um projeto &lt;a href="http://elixir-lang.org/getting-started/mix-otp/dependencies-and-umbrella-apps.html"target="_blank" rel="noopener"&gt;Elixir Umbrella&lt;/a&gt;. Você pode encontrar meu código &lt;a href="https://github.com/akitaonrails/ex_messenger_exercise/blob/master/apps/ex_messenger_client/lib/ex_messenger_client.ex"target="_blank" rel="noopener"&gt;aqui no Github&lt;/a&gt;.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Eu estava me exercitando seguindo este <a href="http://drew.kerrigan.io/ditributed-elixir/"target="_blank" rel="noopener">post antigo de 2014</a> do Drew Kerrigan, onde ele constrói um chat bem simplório, baseado em linha de comando, com um cliente que envia mensagens e comandos para um servidor.</p>
<p>Isso é Elixir pré-1.0, e como é um exercício eu refatorei o código original e juntei os projetos do servidor (ex_messenger) e do cliente (ex_messenger_client) em um projeto <a href="http://elixir-lang.org/getting-started/mix-otp/dependencies-and-umbrella-apps.html"target="_blank" rel="noopener">Elixir Umbrella</a>. Você pode encontrar meu código <a href="https://github.com/akitaonrails/ex_messenger_exercise/blob/master/apps/ex_messenger_client/lib/ex_messenger_client.ex"target="_blank" rel="noopener">aqui no Github</a>.</p>
<p>Se você tem várias aplicações que trabalham juntas e compartilham as mesmas dependências, dá pra usar a convenção Umbrella pra ter todas elas no mesmo code base. Se você roda <tt>mix compile</tt> a partir da raiz do umbrella, ele compila todas as apps (que são projetos mix Elixir independentes também). É só uma forma de ter apps relacionadas no mesmo lugar em vez de em vários repositórios diferentes.</p>
<p>O código mostrado aqui está no <a href="https://github.com/akitaonrails/ex_messenger_exercise"target="_blank" rel="noopener">meu repositório pessoal no Github</a> caso você queira clonar.</p>
<h3>Nodes 101<span class="hx:absolute hx:-mt-20" id="nodes-101"></span>
    <a href="#nodes-101" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Antes de checarmos o exercício, tem um conceito que eu preciso esclarecer. No artigo anterior eu expliquei como você pode iniciar processos e trocar mensagens, e como pode usar GenServer e Supervisor do OTP pra criar processos mais robustos e tolerantes a falhas.</p>
<p>Mas isso é só o começo da história. Você provavelmente já ouviu falar como Erlang também é ótimo pra computação distribuída. Cada VM Erlang (ou BEAM) já vem habilitada pra rede.</p>
<p>De novo, esse é mais um conceito que eu ainda estou começando a aprender direito, e você vai querer ler a documentação do site do Elixir sobre <a href="http://elixir-lang.org/getting-started/mix-otp/distributed-tasks-and-configuration.html"target="_blank" rel="noopener">Distributed tasks and configuration</a>, que faz um excelente trabalho explicando como tudo isso funciona.</p>
<p>Mas só pra começar, você pode simplesmente abrir 2 sessões de IEx. De um terminal você pode fazer:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>iex --sname fabio --cookie chat

Erlang/OTP 18 [erts-7.1] [source] [64-bit] [smp:4:4] [async-threads:10] [kernel-poll:false]

Interactive Elixir (1.1.1) - press Ctrl&#43;C to exit (type h() ENTER for help)
iex(fabio@Hal9000u)1&gt; </code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E de outro terminal diferente você pode fazer:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>iex --sname akita --cookie chat

Erlang/OTP 18 [erts-7.1] [source] [64-bit] [smp:4:4] [async-threads:10] [kernel-poll:false]

Interactive Elixir (1.1.1) - press Ctrl&#43;C to exit (type h() ENTER for help)
iex(akita@Hal9000u)1&gt; </code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Repare como o shell do IEx mostra nomes de Node diferentes pra cada instância: &ldquo;fabio@Hal9000u&rdquo; e &ldquo;akita@Hal9000u&rdquo;. É o sname concatenado com o nome da sua máquina. De uma instância você pode dar ping na outra, por exemplo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>iex(akita@Hal9000u)2&gt; Node.ping(:&#34;fabio@Hal9000u&#34;)
:pong</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Se o nome estiver correto e a outra instância estiver de fato no ar, ela responde o ping com um <tt>:pong</tt>. Isso funciona pra nodes na mesma máquina, mas e se eu precisar conectar numa instância em uma máquina remota?</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>iex(akita@Hal9000u)3&gt; Node.ping(:&#34;fabio@192.168.1.13&#34;)

11:02:46.152 [error] ** System NOT running to use fully qualified hostnames **
** Hostname 192.168.1.13 is illegal **</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>A opção <tt>&ndash;sname</tt> define um nome alcançável apenas dentro da mesma subnet. Pra um nome de domínio totalmente qualificado você precisa usar <tt>&ndash;name</tt>, por exemplo, assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>iex  --name fabio@192.168.1.13 --cookie chat</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E pro outro node:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>iex --name akita@192.168.1.13 --cookie chat</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E desse segundo terminal você pode dar ping no outro node da mesma maneira que antes:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>iex(akita@192.168.1.13)1&gt; Node.ping(:&#34;fabio@192.168.1.13&#34;)
:pong</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E você deve estar se perguntando, que negócio é esse de &ldquo;<tt>&ndash;cookie</tt>&rdquo;? Suba um terceiro terminal com outro nome de cliente, mas sem o cookie, assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>iex --name john@192.168.1.13</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E se você tentar dar ping num dos dois primeiros nodes, não vai receber um <tt>:pong</tt> de volta:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>iex(john@192.168.1.13)1&gt; Node.ping(:&#34;fabio@192.168.1.13&#34;)
:pang</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O cookie é só um atom pra identificar relação entre os nodes. Em um pool de vários servidores, você consegue garantir que não está tentando conectar aplicações diferentes umas com as outras. E como resultado você recebe um <tt>:pang</tt>. No lugar de um endereço IP você pode usar um nome de domínio totalmente qualificado.</p>
<p>E só pelo node &ldquo;akita@&rdquo; ter dado ping no &ldquo;fabio@&rdquo; dá pra ver que eles agora estão cientes um do outro:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>iex(fabio@192.168.1.13)2&gt; Node.list
[:&#34;akita@192.168.1.13&#34;]</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>iex(akita@192.168.1.13)2&gt; Node.list
[:&#34;fabio@192.168.1.13&#34;]</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Se um dos nodes crashar ou sair, a lista de Node é automaticamente atualizada pra refletir somente os nodes que de fato estão vivos e respondendo.</p>
<p>Você pode checar a Referência oficial da API do <a href="http://elixir-lang.org/docs/stable/elixir/Node.html#summary"target="_blank" rel="noopener">Node</a> pra mais informações. Mas isso já deve dar uma dica pro próximo trecho.</p>
<h3>Criando um Cliente de Chat<span class="hx:absolute hx:-mt-20" id="criando-um-cliente-de-chat"></span>
    <a href="#criando-um-cliente-de-chat" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Voltando ao exercício, o servidor ExMessenger tem o &ldquo;ExMessenger.Server&rdquo;, que é um GenServer, e o &ldquo;ExMessenger.Supervisor&rdquo; que sobe ele. O ExMessenger.Server é registrado globalmente como <tt>:message_server</tt>, iniciado e supervisionado pelo &ldquo;ExMessenger.Supervisor&rdquo;.</p>
<p>O &ldquo;ExMessengerClient&rdquo; sobe o &ldquo;ExMessengerClient.MessageHandler&rdquo; (sem supervisão), que também é um GenServer, e registrado globalmente como <tt>:message_handler</tt>.</p>
<p>A Tree pra ambas as apps fica mais ou menos assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>ExMessenger
- ExMessenger.Supervisor
    &#43; ExMessenger.Server
ExMessengerClient
- ExMessengerClient.MessageHandler</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Nós subimos eles separadamente, primeiro o message server:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>cd apps/ex_messenger
iex --sname server --cookie chocolate-chip -S mix run</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Repare que pra esse exemplo estamos subindo com um nome simples &ldquo;server&rdquo;, pra subnet local, e um cookie. Vai responder como &ldquo;server@Hal9000u&rdquo; (Hal9000u sendo o nome da minha máquina local).</p>
<p>Em seguida, podemos subir o app cliente:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>cd apps/ex_messenger_client
server=server@Hal9000u nick=john elixir --sname client -S mix run</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Aqui estamos definindo 2 variáveis de ambiente (que podemos recuperar dentro da app usando <tt>System.get_env/1</tt>) e também definindo o nome local do node como &ldquo;client&rdquo;. Você pode subir mais nodes cliente usando um &ldquo;sname&rdquo; diferente e um &ldquo;nick&rdquo; diferente em outro terminal, quantos você quiser, todos linkando no mesmo message server &ldquo;server@Hal9000u&rdquo;.</p>
<p>Estou subindo desse jeito em vez de usar um escript de linha de comando como fiz no ExMangaDownloadr porque eu não achei nenhuma forma de definir o <tt>&ndash;sname</tt> ou <tt>&ndash;name</tt> da mesma forma que dá pra definir o <tt>&ndash;cookie</tt> usando <tt>Node.set_cookie</tt>. Se alguém souber como configurar isso de outro jeito, me avisa nos comentários aí embaixo.</p>
<p>Repare que eu disse &ldquo;linkando&rdquo; e não &ldquo;conectando&rdquo;. Do &ldquo;ExMessengerClient&rdquo; começamos assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">ExMessengerClient</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">Application</span>
</span></span><span class="line"><span class="cl">  <span class="k">alias</span> <span class="no">ExMessengerClient</span><span class="o">.</span><span class="n">CLI</span>
</span></span><span class="line"><span class="cl">  <span class="k">alias</span> <span class="no">ExMessengerClient</span><span class="o">.</span><span class="n">ServerProcotol</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">start</span><span class="p">(</span><span class="n">_type</span><span class="p">,</span> <span class="n">_args</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">get_env</span>
</span></span><span class="line"><span class="cl">      <span class="o">|&gt;</span> <span class="n">connect</span>
</span></span><span class="line"><span class="cl">      <span class="o">|&gt;</span> <span class="n">start_message_handler</span>
</span></span><span class="line"><span class="cl">      <span class="o">|&gt;</span> <span class="n">join_chatroom</span>
</span></span><span class="line"><span class="cl">      <span class="o">|&gt;</span> <span class="no">CLI</span><span class="o">.</span><span class="n">input_loop</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>A função privada <tt>get_env</tt> é só um wrapper pra tratar as variáveis de ambiente &ldquo;server&rdquo; e &ldquo;nick&rdquo; que passamos:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defp</span> <span class="n">get_env</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">server</span> <span class="o">=</span> <span class="no">System</span><span class="o">.</span><span class="n">get_env</span><span class="p">(</span><span class="s2">&#34;server&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="nb">String</span><span class="o">.</span><span class="n">rstrip</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="nb">String</span><span class="o">.</span><span class="n">to_atom</span>
</span></span><span class="line"><span class="cl">  <span class="n">nick</span> <span class="o">=</span> <span class="no">System</span><span class="o">.</span><span class="n">get_env</span><span class="p">(</span><span class="s2">&#34;nick&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="nb">String</span><span class="o">.</span><span class="n">rstrip</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span><span class="n">server</span><span class="p">,</span> <span class="n">nick</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora, tentamos conectar no servidor remoto:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defp</span> <span class="n">connect</span><span class="p">({</span><span class="n">server</span><span class="p">,</span> <span class="n">nick</span><span class="p">})</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="no">IO</span><span class="o">.</span><span class="n">puts</span> <span class="s2">&#34;Connecting to </span><span class="si">#{</span><span class="n">server</span><span class="si">}</span><span class="s2"> from </span><span class="si">#{</span><span class="no">Node</span><span class="o">.</span><span class="n">self</span><span class="si">}</span><span class="s2"> ...&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="no">Node</span><span class="o">.</span><span class="n">set_cookie</span><span class="p">(</span><span class="no">Node</span><span class="o">.</span><span class="n">self</span><span class="p">,</span> <span class="ss">:&#34;chocolate-chip&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">case</span> <span class="no">Node</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="n">server</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="kp">true</span> <span class="o">-&gt;</span> <span class="ss">:ok</span>
</span></span><span class="line"><span class="cl">    <span class="n">reason</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="no">IO</span><span class="o">.</span><span class="n">puts</span> <span class="s2">&#34;Could not connect to server, reason: </span><span class="si">#{</span><span class="n">reason</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="no">System</span><span class="o">.</span><span class="n">halt</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span><span class="n">server</span><span class="p">,</span> <span class="n">nick</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>A parte importante aqui é que estamos definindo o cookie da instância do cliente com <tt>Node.set_cookie/1</tt> (repare que não passamos ele nas opções de linha de comando como fizemos com a instância do servidor). Sem definir o cookie, a próxima linha com <tt>Node.connect(server)</tt> falharia em conectar, como expliquei na seção anterior.</p>
<p>Em seguida, subimos o GenServer &ldquo;ExMessengerClient.MessageHandler&rdquo;, linkando com a instância do Message Server:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defp</span> <span class="n">start_message_handler</span><span class="p">({</span><span class="n">server</span><span class="p">,</span> <span class="n">nick</span><span class="p">})</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="no">ExMessengerClient</span><span class="o">.</span><span class="n">MessageHandler</span><span class="o">.</span><span class="n">start_link</span><span class="p">(</span><span class="n">server</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="no">IO</span><span class="o">.</span><span class="n">puts</span> <span class="s2">&#34;Connected&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span><span class="n">server</span><span class="p">,</span> <span class="n">nick</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O GenServer Message Handler em si é bem simples, ele só guarda o servidor como estado e trata mensagens recebidas do servidor, imprimindo no terminal do cliente:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">ExMessengerClient</span><span class="o">.</span><span class="n">MessageHandler</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">GenServer</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">start_link</span><span class="p">(</span><span class="n">server</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="ss">:gen_server</span><span class="o">.</span><span class="n">start_link</span><span class="p">({</span> <span class="ss">:local</span><span class="p">,</span> <span class="ss">:message_handler</span> <span class="p">},</span> <span class="n">__MODULE__</span><span class="p">,</span> <span class="n">server</span><span class="p">,</span> <span class="o">[]</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">init</span><span class="p">(</span><span class="n">server</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="ss">:ok</span><span class="p">,</span> <span class="n">server</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">handle_cast</span><span class="p">({</span> <span class="ss">:message</span><span class="p">,</span> <span class="n">nick</span><span class="p">,</span> <span class="n">message</span> <span class="p">},</span> <span class="n">server</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">message</span> <span class="o">=</span> <span class="n">message</span> <span class="o">|&gt;</span> <span class="nb">String</span><span class="o">.</span><span class="n">rstrip</span>
</span></span><span class="line"><span class="cl">    <span class="no">IO</span><span class="o">.</span><span class="n">puts</span> <span class="s2">&#34;</span><span class="se">\n</span><span class="si">#{</span><span class="n">server</span><span class="si">}</span><span class="s2">&gt; </span><span class="si">#{</span><span class="n">nick</span><span class="si">}</span><span class="s2">: </span><span class="si">#{</span><span class="n">message</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="no">IO</span><span class="o">.</span><span class="n">write</span> <span class="s2">&#34;</span><span class="si">#{</span><span class="no">Node</span><span class="o">.</span><span class="n">self</span><span class="si">}</span><span class="s2">&gt; &#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="ss">:noreply</span><span class="p">,</span> <span class="n">server</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Voltando pro módulo principal &ldquo;ExMessengerClient&rdquo;, depois de subir o GenServer (não supervisionado) que recebe as mensagens entrantes, partimos pra entrar na pseudo-chatroom no servidor:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defp</span> <span class="n">join_chatroom</span><span class="p">({</span><span class="n">server</span><span class="p">,</span> <span class="n">nick</span><span class="p">})</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="k">case</span> <span class="no">ServerProcotol</span><span class="o">.</span><span class="n">connect</span><span class="p">({</span><span class="n">server</span><span class="p">,</span> <span class="n">nick</span><span class="p">})</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">users</span><span class="p">}</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="no">IO</span><span class="o">.</span><span class="n">puts</span> <span class="s2">&#34;* Joined the chatroom *&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="no">IO</span><span class="o">.</span><span class="n">puts</span> <span class="s2">&#34;* Users in the room: </span><span class="si">#{</span><span class="n">users</span><span class="si">}</span><span class="s2"> *&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="no">IO</span><span class="o">.</span><span class="n">puts</span> <span class="s2">&#34;* Type /help for options *&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">reason</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="no">IO</span><span class="o">.</span><span class="n">puts</span> <span class="s2">&#34;Could not join chatroom, reason: </span><span class="si">#{</span><span class="n">reason</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="no">System</span><span class="o">.</span><span class="n">halt</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span><span class="n">server</span><span class="p">,</span> <span class="n">nick</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Eu defini esse módulo &ldquo;ServerProcotol&rdquo; que é só um wrapper de conveniência pra chamadas <tt>GenServer.call/3</tt> e <tt>GenServer.cast/2</tt>, pra mandar mensagens pro GenServer remoto chamado <tt>:message_server</tt>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">ExMessengerClient</span><span class="o">.</span><span class="n">ServerProcotol</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">connect</span><span class="p">({</span><span class="n">server</span><span class="p">,</span> <span class="n">nick</span><span class="p">})</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">server</span> <span class="o">|&gt;</span> <span class="n">call</span><span class="p">({</span><span class="ss">:connect</span><span class="p">,</span> <span class="n">nick</span><span class="p">})</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">disconnect</span><span class="p">({</span><span class="n">server</span><span class="p">,</span> <span class="n">nick</span><span class="p">})</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">server</span> <span class="o">|&gt;</span> <span class="n">call</span><span class="p">({</span><span class="ss">:disconnect</span><span class="p">,</span> <span class="n">nick</span><span class="p">})</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">list_users</span><span class="p">({</span><span class="n">server</span><span class="p">,</span> <span class="n">nick</span><span class="p">})</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">server</span> <span class="o">|&gt;</span> <span class="n">cast</span><span class="p">({</span><span class="ss">:list_users</span><span class="p">,</span> <span class="n">nick</span><span class="p">})</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">private_message</span><span class="p">({</span><span class="n">server</span><span class="p">,</span> <span class="n">nick</span><span class="p">},</span> <span class="n">to</span><span class="p">,</span> <span class="n">message</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">server</span> <span class="o">|&gt;</span> <span class="n">cast</span><span class="p">({</span><span class="ss">:private_message</span><span class="p">,</span> <span class="n">nick</span><span class="p">,</span> <span class="n">to</span><span class="p">,</span> <span class="n">message</span><span class="p">})</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">say</span><span class="p">({</span><span class="n">server</span><span class="p">,</span> <span class="n">nick</span><span class="p">},</span> <span class="n">message</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">server</span> <span class="o">|&gt;</span> <span class="n">cast</span><span class="p">({</span><span class="ss">:say</span><span class="p">,</span> <span class="n">nick</span><span class="p">,</span> <span class="n">message</span><span class="p">})</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">defp</span> <span class="n">call</span><span class="p">(</span><span class="n">server</span><span class="p">,</span> <span class="n">args</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="no">GenServer</span><span class="o">.</span><span class="n">call</span><span class="p">({</span><span class="ss">:message_server</span><span class="p">,</span> <span class="n">server</span><span class="p">},</span> <span class="n">args</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">defp</span> <span class="n">cast</span><span class="p">(</span><span class="n">server</span><span class="p">,</span> <span class="n">args</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="no">GenServer</span><span class="o">.</span><span class="n">cast</span><span class="p">({</span><span class="ss">:message_server</span><span class="p">,</span> <span class="n">server</span><span class="p">},</span> <span class="n">args</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Bem direto. Em seguida, o ExMessengerClient principal chama a função recursiva <tt>input_loop/1</tt> do módulo CLI, que só recebe input do usuário e trata os comandos apropriados usando pattern matching, assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">ExMessengerClient</span><span class="o">.</span><span class="n">CLI</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="k">alias</span> <span class="no">ExMessengerClient</span><span class="o">.</span><span class="n">ServerProcotol</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">input_loop</span><span class="p">({</span><span class="n">server</span><span class="p">,</span> <span class="n">nick</span><span class="p">})</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="no">IO</span><span class="o">.</span><span class="n">write</span> <span class="s2">&#34;</span><span class="si">#{</span><span class="no">Node</span><span class="o">.</span><span class="n">self</span><span class="si">}</span><span class="s2">&gt; &#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">line</span> <span class="o">=</span> <span class="no">IO</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="ss">:line</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="o">|&gt;</span> <span class="nb">String</span><span class="o">.</span><span class="n">rstrip</span>
</span></span><span class="line"><span class="cl">    <span class="n">handle_command</span> <span class="n">line</span><span class="p">,</span> <span class="p">{</span><span class="n">server</span><span class="p">,</span> <span class="n">nick</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="n">input_loop</span> <span class="p">{</span><span class="n">server</span><span class="p">,</span> <span class="n">nick</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">handle_command</span><span class="p">(</span><span class="s2">&#34;/help&#34;</span><span class="p">,</span> <span class="n">_args</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="no">IO</span><span class="o">.</span><span class="n">puts</span> <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">    Available commands:
</span></span></span><span class="line"><span class="cl"><span class="s2">      /leave
</span></span></span><span class="line"><span class="cl"><span class="s2">      /join
</span></span></span><span class="line"><span class="cl"><span class="s2">      /users
</span></span></span><span class="line"><span class="cl"><span class="s2">      /pm &lt;to nick&gt; &lt;message&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">      or just type a message to send
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">handle_command</span><span class="p">(</span><span class="s2">&#34;/leave&#34;</span><span class="p">,</span> <span class="n">args</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="no">ServerProcotol</span><span class="o">.</span><span class="n">disconnect</span><span class="p">(</span><span class="n">args</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="no">IO</span><span class="o">.</span><span class="n">puts</span> <span class="s2">&#34;You have exited the chatroom, you can rejoin with /join or quit with /quit&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">handle_command</span><span class="p">(</span><span class="s2">&#34;/quit&#34;</span><span class="p">,</span> <span class="n">args</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="no">ServerProcotol</span><span class="o">.</span><span class="n">disconnect</span><span class="p">(</span><span class="n">args</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="no">System</span><span class="o">.</span><span class="n">halt</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">handle_command</span><span class="p">(</span><span class="s2">&#34;/join&#34;</span><span class="p">,</span> <span class="n">args</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="no">ServerProcotol</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="n">args</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="no">IO</span><span class="o">.</span><span class="n">puts</span> <span class="s2">&#34;Joined the chatroom&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">handle_command</span><span class="p">(</span><span class="s2">&#34;/users&#34;</span><span class="p">,</span> <span class="n">args</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="no">ServerProcotol</span><span class="o">.</span><span class="n">list_users</span><span class="p">(</span><span class="n">args</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">handle_command</span><span class="p">(</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="n">_args</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="ss">:ok</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">handle_command</span><span class="p">(</span><span class="kp">nil</span><span class="p">,</span> <span class="n">_args</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="ss">:ok</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">handle_command</span><span class="p">(</span><span class="n">message</span><span class="p">,</span> <span class="n">args</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nb">String</span><span class="o">.</span><span class="n">contains?</span><span class="p">(</span><span class="n">message</span><span class="p">,</span> <span class="s2">&#34;/pm&#34;</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span><span class="n">to</span><span class="p">,</span> <span class="n">message</span><span class="p">}</span> <span class="o">=</span> <span class="n">parse_private_recipient</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="no">ServerProcotol</span><span class="o">.</span><span class="n">private_message</span><span class="p">(</span><span class="n">args</span><span class="p">,</span> <span class="n">to</span><span class="p">,</span> <span class="n">message</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span>
</span></span><span class="line"><span class="cl">      <span class="no">ServerProcotol</span><span class="o">.</span><span class="n">say</span><span class="p">(</span><span class="n">args</span><span class="p">,</span> <span class="n">message</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">defp</span> <span class="n">parse_private_recipient</span><span class="p">(</span><span class="n">message</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="o">[</span><span class="n">to</span><span class="o">|</span><span class="n">message</span><span class="o">]</span> <span class="o">=</span> <span class="n">message</span>
</span></span><span class="line"><span class="cl">      <span class="o">|&gt;</span> <span class="nb">String</span><span class="o">.</span><span class="n">slice</span><span class="p">(</span><span class="mi">4</span><span class="o">..-</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="o">|&gt;</span> <span class="nb">String</span><span class="o">.</span><span class="n">split</span>
</span></span><span class="line"><span class="cl">    <span class="n">message</span> <span class="o">=</span> <span class="n">message</span>
</span></span><span class="line"><span class="cl">      <span class="o">|&gt;</span> <span class="no">List</span><span class="o">.</span><span class="n">foldl</span><span class="p">(</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="n">fn</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">acc</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="s2">&#34;</span><span class="si">#{</span><span class="n">acc</span><span class="si">}</span><span class="s2"> </span><span class="si">#{</span><span class="n">x</span><span class="si">}</span><span class="s2">&#34;</span> <span class="k">end</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="o">|&gt;</span> <span class="nb">String</span><span class="o">.</span><span class="n">lstrip</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="n">to</span><span class="p">,</span> <span class="n">message</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E isso encerra o Cliente.</p>
<h3>Criando um Servidor de Chat<span class="hx:absolute hx:-mt-20" id="criando-um-servidor-de-chat"></span>
    <a href="#criando-um-servidor-de-chat" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O Chat Client manda mensagens GenServer pra um <tt>{:message_server, server}</tt> remoto, e no exemplo, <tt>server</tt> é só o atom do sname &ldquo;server@Hal9000u&rdquo;.</p>
<p>Agora, precisamos desse <tt>:message_server</tt> e ele é o GenServer &ldquo;ExMessenger.Server&rdquo;:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">ExMessenger</span><span class="o">.</span><span class="n">Server</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">GenServer</span>
</span></span><span class="line"><span class="cl">  <span class="nb">require</span> <span class="no">Logger</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">start_link</span><span class="p">(</span><span class="o">[]</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="ss">:gen_server</span><span class="o">.</span><span class="n">start_link</span><span class="p">({</span> <span class="ss">:local</span><span class="p">,</span> <span class="ss">:message_server</span> <span class="p">},</span> <span class="n">__MODULE__</span><span class="p">,</span> <span class="o">[]</span><span class="p">,</span> <span class="o">[]</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">init</span><span class="p">(</span><span class="o">[]</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="ss">:ok</span><span class="p">,</span> <span class="no">HashDict</span><span class="o">.</span><span class="n">new</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E é isso, quando o &ldquo;ExMessenger.Supervisor&rdquo; sobe esse GenServer, ele registra globalmente nessa instância como <tt>:message_server</tt>. E é assim que endereçamos mensagens vindas do que chamamos de &ldquo;clientes&rdquo; (a aplicação ExMessengerClient).</p>
<p>Quando o ExMessengerClient chama o <tt>ServerProtocol.connect/1</tt>, ele manda a mensagem <tt>{:connect, nick}</tt> pro servidor. No Servidor, tratamos assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">handle_call</span><span class="p">({</span> <span class="ss">:connect</span><span class="p">,</span> <span class="n">nick</span> <span class="p">},</span> <span class="p">{</span><span class="n">from</span><span class="p">,</span> <span class="n">_</span><span class="p">}</span> <span class="p">,</span> <span class="n">users</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">cond</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">nick</span> <span class="o">==</span> <span class="ss">:server</span> <span class="ow">or</span> <span class="n">nick</span> <span class="o">==</span> <span class="s2">&#34;server&#34;</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span><span class="ss">:reply</span><span class="p">,</span> <span class="ss">:nick_not_allowed</span><span class="p">,</span> <span class="n">users</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="no">HashDict</span><span class="o">.</span><span class="n">has_key?</span><span class="p">(</span><span class="n">users</span><span class="p">,</span> <span class="n">nick</span><span class="p">)</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span><span class="ss">:reply</span><span class="p">,</span> <span class="ss">:nick_in_use</span><span class="p">,</span> <span class="n">users</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kp">true</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="n">new_users</span> <span class="o">=</span> <span class="n">users</span> <span class="o">|&gt;</span> <span class="no">HashDict</span><span class="o">.</span><span class="n">put</span><span class="p">(</span><span class="n">nick</span><span class="p">,</span> <span class="n">node</span><span class="p">(</span><span class="n">from</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">      <span class="n">user_list</span> <span class="o">=</span> <span class="n">log</span><span class="p">(</span><span class="n">new_users</span><span class="p">,</span> <span class="n">nick</span><span class="p">,</span> <span class="s2">&#34;has joined&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span><span class="ss">:reply</span><span class="p">,</span> <span class="p">{</span> <span class="ss">:ok</span><span class="p">,</span> <span class="n">user_list</span> <span class="p">},</span> <span class="n">new_users</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Primeiro, ele checa se o nick é &ldquo;server&rdquo; e proíbe. Segundo, checa se o nickname já existe no <a href="http://elixir-lang.org/docs/stable/elixir/HashDict.html"target="_blank" rel="noopener">HashDict</a> interno (um dicionário chave/valor) e recusa caso já exista. Por fim, em terceiro, coloca o par de nickname e nome do node (tipo &ldquo;client@Hal9000u&rdquo;) no HashDict e faz broadcast através da função privada <tt>log/3</tt> pra todos os outros nodes no dicionário HashDict.</p>
<p>O <tt>log/3</tt> só monta uma mensagem de log concatenando os nicks de todos os clientes e imprime ela, depois faz broadcast disso pro Message Handler de todos os clientes listados no HashDict:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defp</span> <span class="n">log</span><span class="p">(</span><span class="n">users</span><span class="p">,</span> <span class="n">nick</span><span class="p">,</span> <span class="n">message</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">user_list</span> <span class="o">=</span> <span class="n">users</span> <span class="o">|&gt;</span> <span class="no">HashDict</span><span class="o">.</span><span class="n">keys</span> <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="s2">&#34;:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="no">Logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&#34;</span><span class="si">#{</span><span class="n">nick</span><span class="si">}</span><span class="s2"> </span><span class="si">#{</span><span class="n">message</span><span class="si">}</span><span class="s2">, user_list: </span><span class="si">#{</span><span class="n">user_list</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">say</span><span class="p">(</span><span class="n">nick</span><span class="p">,</span> <span class="n">message</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">user_list</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">say</span><span class="p">(</span><span class="n">nick</span><span class="p">,</span> <span class="n">message</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="no">GenServer</span><span class="o">.</span><span class="n">cast</span><span class="p">(</span><span class="ss">:message_server</span><span class="p">,</span> <span class="p">{</span> <span class="ss">:say</span><span class="p">,</span> <span class="n">nick</span><span class="p">,</span> <span class="s2">&#34;* </span><span class="si">#{</span><span class="n">nick</span><span class="si">}</span><span class="s2"> </span><span class="si">#{</span><span class="n">message</span><span class="si">}</span><span class="s2"> *&#34;</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">handle_cast</span><span class="p">({</span> <span class="ss">:say</span><span class="p">,</span> <span class="n">nick</span><span class="p">,</span> <span class="n">message</span> <span class="p">},</span> <span class="n">users</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">ears</span> <span class="o">=</span> <span class="no">HashDict</span><span class="o">.</span><span class="n">delete</span><span class="p">(</span><span class="n">users</span><span class="p">,</span> <span class="n">nick</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="no">Logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&#34;</span><span class="si">#{</span><span class="n">nick</span><span class="si">}</span><span class="s2"> said </span><span class="si">#{</span><span class="n">message</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">broadcast</span><span class="p">(</span><span class="n">ears</span><span class="p">,</span> <span class="n">nick</span><span class="p">,</span> <span class="n">message</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span><span class="ss">:noreply</span><span class="p">,</span> <span class="n">users</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Até esse ponto ele só faz cast de uma mensagem pra ele mesmo, a tupla <tt>{:say, nick, message}</tt>, que é tratada pelo GenServer chamando a função <tt>broadcast/3</tt> definida assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defp</span> <span class="n">broadcast</span><span class="p">(</span><span class="n">users</span><span class="p">,</span> <span class="n">nick</span><span class="p">,</span> <span class="n">message</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">users</span><span class="p">,</span> <span class="n">fn</span> <span class="p">{</span><span class="n">_</span><span class="p">,</span> <span class="n">node</span><span class="p">}</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="no">Task</span><span class="o">.</span><span class="n">async</span><span class="p">(</span><span class="n">fn</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="n">send_message_to_client</span><span class="p">(</span><span class="n">node</span><span class="p">,</span> <span class="n">nick</span><span class="p">,</span> <span class="n">message</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="no">Task</span><span class="o">.</span><span class="n">await</span><span class="o">/</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">defp</span> <span class="n">send_message_to_client</span><span class="p">(</span><span class="n">client_node</span><span class="p">,</span> <span class="n">nick</span><span class="p">,</span> <span class="n">message</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="no">GenServer</span><span class="o">.</span><span class="n">cast</span><span class="p">({</span> <span class="ss">:message_handler</span><span class="p">,</span> <span class="n">client_node</span> <span class="p">},</span> <span class="p">{</span> <span class="ss">:message</span><span class="p">,</span> <span class="n">nick</span><span class="p">,</span> <span class="n">message</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Ele mapeia a lista de usuários e dispara uma <tt>Task</tt> Elixir assíncrona (que ela mesma é só um GenServer, como expliquei antes na série Ex Manga Downloader). Como é um broadcast, faz sentido fazer todas elas em paralelo.</p>
<p>A parte importante é o <tt>send_message_to_client/3</tt> que faz cast de uma mensagem pra tupla <tt>{ :message_handler, client_node }</tt> onde &ldquo;client_node&rdquo; é só &ldquo;client@Hal9000u&rdquo; ou qualquer outro &ldquo;&ndash;sname&rdquo; que você usou pra subir cada node cliente.</p>
<p>Pronto, é assim que os clientes mandam calls/casts de GenServer pra <tt>{:message_server, server}</tt> e ele manda mensagens de volta pra <tt>{:message_handler, client</tt>.</p>
<h3>Esse não é o seu exemplo tradicional de Cliente/Servidor TCP!<span class="hx:absolute hx:-mt-20" id="esse-não-é-o-seu-exemplo-tradicional-de-clienteservidor-tcp"></span>
    <a href="#esse-n%c3%a3o-%c3%a9-o-seu-exemplo-tradicional-de-clienteservidor-tcp" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Agora, estamos chamando o &ldquo;ExMessenger.Server&rdquo; de &ldquo;Servidor&rdquo; de Chat e o &ldquo;ExMessengerClient&rdquo; de &ldquo;Cliente&rdquo; de Chat. Apesar de termos chamado eles de &ldquo;Servidor&rdquo; e &ldquo;Cliente&rdquo;, eles não se referem aos exemplos comuns de &ldquo;Servidor TCP&rdquo; e &ldquo;Cliente TCP&rdquo; com os quais você talvez esteja familiarizado!</p>
<p>O ExMessenger.Server é de fato um Servidor (um GenServer OTP) mas o ExMessengerClient.MessageHandler também é um Servidor (outro GenServer OTP)! Mas como <strong>ambos</strong> têm comportamento de Node, eles são mais como 2 nodes peer-to-peer do que aquela velha relação simples de cliente-&gt;servidor da escola antiga. Eles podem ter comportamento de cliente (o Server manda mensagens pro MessageHandler) e comportamento de servidor (o Server recebendo mensagens do ExMessengerClient).</p>
<p>Deixa esse conceito assentar por um momento: já embutido na linguagem você ganha um modelo completo de distribuição de rede peer-to-peer, fácil de usar. Você não precisa ter um único node eleito como o &ldquo;node&rdquo; exclusivo. Você poderia ter todos os nodes em um anel se coordenando entre si, evitando pontos únicos de falha.</p>
<p>Eu acredito que talvez seja assim que serviços baseados em Erlang como <a href="http://manpages.ubuntu.com/manpages/hardy/man8/ejabberd.8.html"target="_blank" rel="noopener">ejabberd</a> e <a href="https://www.rabbitmq.com/clustering.html"target="_blank" rel="noopener">RabbitMQ</a> funcionam.</p>
<p>No <a href="https://github.com/processone/ejabberd/blob/master/src/ejabberd_cluster.erl"target="_blank" rel="noopener">caso do ejabberd</a>, dá pra ver que ele mantém o estado do cluster em tabelas Mnesia (Mnesia sendo mais um componente do OTP, é um banco NoSQL distribuído já embutido!) e ele de fato usa as facilidades de Node pra coordenar nodes distribuídos:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-erlang" data-lang="erlang"><span class="line"><span class="cl"><span class="p">...</span>
</span></span><span class="line"><span class="cl"><span class="nf">join</span><span class="p">(</span><span class="nv">Node</span><span class="p">)</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="p">{</span><span class="nb">node</span><span class="p">(),</span> <span class="nn">net_adm</span><span class="p">:</span><span class="nf">ping</span><span class="p">(</span><span class="nv">Node</span><span class="p">)}</span> <span class="k">of</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span><span class="nv">Node</span><span class="p">,</span> <span class="p">_}</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span><span class="n">error</span><span class="p">,</span> <span class="p">{</span><span class="n">not_master</span><span class="p">,</span> <span class="nv">Node</span><span class="p">}};</span>
</span></span><span class="line"><span class="cl">        <span class="p">{_,</span> <span class="n">pong</span><span class="p">}</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="nn">application</span><span class="p">:</span><span class="nf">stop</span><span class="p">(</span><span class="n">ejabberd</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="nn">application</span><span class="p">:</span><span class="nf">stop</span><span class="p">(</span><span class="n">mnesia</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="nn">mnesia</span><span class="p">:</span><span class="nf">delete_schema</span><span class="p">([</span><span class="nb">node</span><span class="p">()]),</span>
</span></span><span class="line"><span class="cl">            <span class="nn">application</span><span class="p">:</span><span class="nf">start</span><span class="p">(</span><span class="n">mnesia</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="nn">mnesia</span><span class="p">:</span><span class="nf">change_config</span><span class="p">(</span><span class="n">extra_db_nodes</span><span class="p">,</span> <span class="p">[</span><span class="nv">Node</span><span class="p">]),</span>
</span></span><span class="line"><span class="cl">            <span class="nn">mnesia</span><span class="p">:</span><span class="nf">change_table_copy_type</span><span class="p">(</span><span class="n">schema</span><span class="p">,</span> <span class="nb">node</span><span class="p">(),</span> <span class="n">disc_copies</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="nb">spawn</span><span class="p">(</span><span class="k">fun</span><span class="p">()</span>  <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">                <span class="nn">lists</span><span class="p">:</span><span class="nf">foreach</span><span class="p">(</span><span class="k">fun</span><span class="p">(</span><span class="nv">Table</span><span class="p">)</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">                            <span class="nv">Type</span> <span class="o">=</span> <span class="n">call</span><span class="p">(</span><span class="nv">Node</span><span class="p">,</span> <span class="n">mnesia</span><span class="p">,</span> <span class="n">table_info</span><span class="p">,</span> <span class="p">[</span><span class="nv">Table</span><span class="p">,</span> <span class="n">storage_type</span><span class="p">]),</span>
</span></span><span class="line"><span class="cl">                            <span class="nn">mnesia</span><span class="p">:</span><span class="nf">add_table_copy</span><span class="p">(</span><span class="nv">Table</span><span class="p">,</span> <span class="nb">node</span><span class="p">(),</span> <span class="nv">Type</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                    <span class="k">end</span><span class="p">,</span> <span class="nn">mnesia</span><span class="p">:</span><span class="nb">system_info</span><span class="p">(</span><span class="n">tables</span><span class="p">)</span><span class="o">--</span><span class="p">[</span><span class="n">schema</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">                <span class="k">end</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="nn">application</span><span class="p">:</span><span class="nf">start</span><span class="p">(</span><span class="n">ejabberd</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">_</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span><span class="n">error</span><span class="p">,</span> <span class="p">{</span><span class="n">no_ping</span><span class="p">,</span> <span class="nv">Node</span><span class="p">}}</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span><span class="p">.</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>É assim que um trecho de código fonte Erlang puro fica, aliás. Você já deve ter Elixir suficiente na cabeça pra conseguir abstrair a sintaxe feia do Erlang e ver que é um <tt>case</tt> fazendo pattern matching na tupla <tt>{_, :pong}</tt>, usando as facilidades de ping do Node pra atestar a conectividade do node e atualizar a tabela Mnesia e outras configurações.</p>
<p>Também no <a href="https://github.com/rabbitmq/rabbitmq-server/blob/6f70dcbe05dbba35f7d950674d293a4c7d867d44/src/rabbit_control_main.erl"target="_blank" rel="noopener">código fonte do RabbitMQ-Server</a> você vai encontrar uma coisa parecida:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-erlang" data-lang="erlang"><span class="line"><span class="cl"><span class="nf">become</span><span class="p">(</span><span class="nv">BecomeNode</span><span class="p">)</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nn">error_logger</span><span class="p">:</span><span class="nf">tty</span><span class="p">(</span><span class="n">false</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">ok</span> <span class="o">=</span> <span class="nn">net_kernel</span><span class="p">:</span><span class="nf">stop</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="nn">net_adm</span><span class="p">:</span><span class="nf">ping</span><span class="p">(</span><span class="nv">BecomeNode</span><span class="p">)</span> <span class="k">of</span>
</span></span><span class="line"><span class="cl">        <span class="n">pong</span> <span class="o">-&gt;</span> <span class="nb">exit</span><span class="p">({</span><span class="n">node_running</span><span class="p">,</span> <span class="nv">BecomeNode</span><span class="p">});</span>
</span></span><span class="line"><span class="cl">        <span class="n">pang</span> <span class="o">-&gt;</span> <span class="nn">io</span><span class="p">:</span><span class="nf">format</span><span class="p">(</span><span class="s">&#34;  * Impersonating node: </span><span class="si">~s</span><span class="s">...&#34;</span><span class="p">,</span> <span class="p">[</span><span class="nv">BecomeNode</span><span class="p">]),</span>
</span></span><span class="line"><span class="cl">                <span class="p">{</span><span class="n">ok</span><span class="p">,</span> <span class="p">_}</span> <span class="o">=</span> <span class="nn">rabbit_cli</span><span class="p">:</span><span class="nf">start_distribution</span><span class="p">(</span><span class="nv">BecomeNode</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="nn">io</span><span class="p">:</span><span class="nf">format</span><span class="p">(</span><span class="s">&#34; done</span><span class="si">~n</span><span class="s">&#34;</span><span class="p">,</span> <span class="p">[]),</span>
</span></span><span class="line"><span class="cl">                <span class="nv">Dir</span> <span class="o">=</span> <span class="nn">mnesia</span><span class="p">:</span><span class="nb">system_info</span><span class="p">(</span><span class="n">directory</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="nn">io</span><span class="p">:</span><span class="nf">format</span><span class="p">(</span><span class="s">&#34;  * Mnesia directory  : </span><span class="si">~s~n</span><span class="s">&#34;</span><span class="p">,</span> <span class="p">[</span><span class="nv">Dir</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span><span class="p">.</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>De novo, dando ping em nodes, usando Mnesia pro estado do servidor. A sintaxe do Erlang é incomum pra maioria de nós: variáveis começam com letra maiúscula (intuitivamente a gente acha que é uma constante), comandos terminam com ponto, em vez da notação com ponto pra chamar funções de um módulo ele usa dois pontos &ldquo;:&rdquo;, diferente de Elixir os parênteses não são opcionais, e por aí vai. Tentar ler código assim mostra o valor de ter Elixir pra desbloquear os poderes escondidos de Erlang.</p>
<p>Então, até esse ponto, você sabe como internamente os processos são spawnados, como eles são orquestrados dentro do framework OTP, e agora como eles podem interagir remotamente através da abstração peer-to-peer de <strong>Node</strong>. E de novo, isso tudo já vem embutido na linguagem. Nenhuma outra linguagem chega nem perto.</p>
]]></content:encoded><category>elixir-e-erlang</category><category>sistemas-distribuidos</category><category>arquitetura-de-software</category></item><item><title>Observando Processos em Elixir - The Little Elixir &amp; OTP Guidebook</title><link>https://akitaonrails.github.io/2015/11/22/observando-processos-em-elixir-the-little-elixir-otp-guidebook/</link><guid isPermaLink="true">https://akitaonrails.github.io/2015/11/22/observando-processos-em-elixir-the-little-elixir-otp-guidebook/</guid><pubDate>Sun, 22 Nov 2015 14:57:00 -0200</pubDate><description>&lt;p&gt;Na minha jornada para entender de verdade como uma aplicação Elixir decente deve ser escrita, estou me exercitando com o excelente &lt;a href="https://www.manning.com/books/the-little-elixir-and-otp-guidebook"target="_blank" rel="noopener"&gt;The Little Elixir &amp;amp; OTP Guidebook&lt;/a&gt;, do Benjamin Tan Wei Hao. Se você está começando agora, é decisão óbvia: compre e estude esse guia. E sim, ajuda muito se você tiver lido antes o &lt;a href="https://pragprog.com/book/elixir/programming-elixir"target="_blank" rel="noopener"&gt;Programming Elixir&lt;/a&gt; do Dave Thomas.&lt;/p&gt;
&lt;p&gt;No meu artigo &lt;a href="http://www.akitaonrails.com/2015/11/19/ex-manga-downloadr-parte-2-poolboy-ao-resgate"target="_blank" rel="noopener"&gt;Ex Manga Downloadr Parte 2&lt;/a&gt; eu explorei como adicionar um controle melhor de pool de processos usando a excelente e robusta biblioteca Poolboy. Um dos exercícios principais do guia é justamente construir uma versão mais simples do Poolboy em Elixir puro (o Poolboy é escrito no bom e velho Erlang).&lt;/p&gt;</description><content:encoded><![CDATA[<p>Na minha jornada para entender de verdade como uma aplicação Elixir decente deve ser escrita, estou me exercitando com o excelente <a href="https://www.manning.com/books/the-little-elixir-and-otp-guidebook"target="_blank" rel="noopener">The Little Elixir &amp; OTP Guidebook</a>, do Benjamin Tan Wei Hao. Se você está começando agora, é decisão óbvia: compre e estude esse guia. E sim, ajuda muito se você tiver lido antes o <a href="https://pragprog.com/book/elixir/programming-elixir"target="_blank" rel="noopener">Programming Elixir</a> do Dave Thomas.</p>
<p>No meu artigo <a href="http://www.akitaonrails.com/2015/11/19/ex-manga-downloadr-parte-2-poolboy-ao-resgate"target="_blank" rel="noopener">Ex Manga Downloadr Parte 2</a> eu explorei como adicionar um controle melhor de pool de processos usando a excelente e robusta biblioteca Poolboy. Um dos exercícios principais do guia é justamente construir uma versão mais simples do Poolboy em Elixir puro (o Poolboy é escrito no bom e velho Erlang).</p>
<p>O objetivo principal deste artigo é introduzir o que <strong>Tolerância a Falhas</strong> significa no Erlang/Elixir, e também é uma desculpa pra eu mostrar o observer do Erlang:</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/528/big_observer-kill.png" alt="Observer"  loading="lazy" /></p>
<p>Sim, o Erlang permite que a gente veja o que está acontecendo dentro do seu runtime e ainda tome ações sobre Processos individuais rodando lá dentro! Que coisa mais legal, não?</p>
<p>Mas antes de mostrar Tolerância a Falhas e o Observer eu preciso explicar o que são Processos e por que eles importam. Você <strong>precisa</strong> entender os seguintes conceitos pra conseguir programar bem em Elixir:</p>
<ol>
<li>Você não tem &ldquo;objetos&rdquo;, que são instâncias em runtime de classes (ou objetos prototípicos, que são cópias de outros objetos). No lugar de &ldquo;Classes&rdquo; você tem coleções de funções organizadas em módulos, sem dependência de estado interno. E no lugar de &ldquo;objetos&rdquo; temos, falando grosseiramente, &ldquo;processos&rdquo;. Por exemplo:</li>
</ol>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">MyProcess</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">start</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">accepting_messages</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">accepting_messages</span><span class="p">(</span><span class="n">state</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">receive</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span><span class="ss">:hello</span><span class="p">,</span> <span class="n">message</span><span class="p">}</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="no">IO</span><span class="o">.</span><span class="n">puts</span> <span class="s2">&#34;Hello, </span><span class="si">#{</span><span class="n">message</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="n">accepting_messages</span><span class="p">(</span><span class="n">state</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span><span class="ss">:counter</span><span class="p">}</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="n">new_state</span> <span class="o">=</span> <span class="n">state</span> <span class="o">+</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">        <span class="no">IO</span><span class="o">.</span><span class="n">puts</span> <span class="s2">&#34;New state is </span><span class="si">#{</span><span class="n">new_state</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="n">accepting_messages</span><span class="p">(</span><span class="n">new_state</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="n">_</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="no">IO</span><span class="o">.</span><span class="n">puts</span> <span class="s2">&#34;What?&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="n">accepting_messages</span><span class="p">(</span><span class="n">state</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<ol start="2">
<li>Podemos executar uma função dentro de outro processo. É assim que damos spawn em um processo novo, concorrente e leve:</li>
</ol>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">iex</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span><span class="o">&gt;</span> <span class="n">pid</span> <span class="o">=</span> <span class="n">spawn</span> <span class="n">fn</span> <span class="o">-&gt;</span> <span class="no">MyProcess</span><span class="o">.</span><span class="n">start</span> <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="c1">#PID&lt;0.87.0&gt;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Quando o <tt>accepting_messages/1</tt> é chamado, ele para no bloco <tt>receive/0</tt>, esperando receber uma nova mensagem. Aí podemos enviar mensagens assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">iex</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span><span class="o">&gt;</span> <span class="nb">send</span> <span class="n">pid</span><span class="p">,</span> <span class="p">{</span><span class="ss">:hello</span><span class="p">,</span> <span class="s2">&#34;world&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="no">Hello</span><span class="p">,</span> <span class="n">world</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Ele recebe o átomo <tt>{:hello, &ldquo;world&rdquo;}</tt>, faz pattern match do valor <tt>&ldquo;world&rdquo;</tt> na variável <tt>message</tt>, concatena a string <tt>&ldquo;Hello, world&rdquo;</tt>, imprime com <tt>IO.puts/1</tt> e recursa pra si mesmo. Chamamos o bloco <tt>receive/0</tt> de novo e bloqueamos, esperando novas mensagens:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">iex</span><span class="p">(</span><span class="mi">4</span><span class="p">)</span><span class="o">&gt;</span> <span class="nb">send</span> <span class="n">pid</span><span class="p">,</span> <span class="p">{</span><span class="ss">:counter</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="no">New</span> <span class="n">state</span> <span class="n">is</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span><span class="ss">:counter</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="n">iex</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span><span class="o">&gt;</span> <span class="nb">send</span> <span class="n">pid</span><span class="p">,</span> <span class="p">{</span><span class="ss">:counter</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="no">New</span> <span class="n">state</span> <span class="n">is</span> <span class="mi">2</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span><span class="ss">:counter</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="n">iex</span><span class="p">(</span><span class="mi">6</span><span class="p">)</span><span class="o">&gt;</span> <span class="nb">send</span> <span class="n">pid</span><span class="p">,</span> <span class="p">{</span><span class="ss">:counter</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="no">New</span> <span class="n">state</span> <span class="n">is</span> <span class="mi">3</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span><span class="ss">:counter</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="n">iex</span><span class="p">(</span><span class="mi">7</span><span class="p">)</span><span class="o">&gt;</span> <span class="nb">send</span> <span class="n">pid</span><span class="p">,</span> <span class="p">{</span><span class="ss">:counter</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="no">New</span> <span class="n">state</span> <span class="n">is</span> <span class="mi">4</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Mandamos a mensagem <tt>{:counter}</tt> pro mesmo pid de novo e, quando ele recebe essa mensagem, pega o valor de <tt>state</tt> do argumento da função, incrementa em 1, imprime o novo estado, e chama a si mesmo passando o novo estado como novo argumento. Ele bloqueia de novo, esperando novas mensagens, e cada vez que recebe a mensagem <tt>{:counter}</tt>, incrementa o estado anterior em mais um e recursa.</p>
<p>É basicamente assim que mantemos estado em Elixir. Se matarmos esse processo e dermos spawn em um novo, ele recomeça do zero (que é o que a função <tt>start/0</tt> faz).</p>
<ol start="3">
<li>
<p>Então, mesmo sem ter &ldquo;objetos&rdquo;, você tem Processos. Superficialmente, um processo se comporta como um &ldquo;objeto&rdquo;. Cuidado pra não pensar que um Processo é como uma Thread pesada. O Erlang tem seu próprio scheduler interno que controla a concorrência dos paralelos e você pode carregar até 16 bilhões de processos leves se o seu hardware permitir. Threads são super pesadas, processos Erlang são super leves.</p>
</li>
<li>
<p>Como vimos no exemplo, cada processo tem seu próprio mecanismo interno pra receber mensagens de outros processos. Essas mensagens se acumulam numa &ldquo;caixa de mensagens&rdquo; interna e você pode escolher dar <tt>receive</tt> e fazer pattern match nelas, recursando em si mesmo pra receber novas mensagens, se quiser.</p>
</li>
<li>
<p>Processos podem ser linkados a outros processos ou monitorá-los. Por exemplo, dentro de um shell IEx, estamos dentro de um processo Elixir, então podemos fazer:</p>
</li>
</ol>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">iex</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">&gt;</span> <span class="nb">self</span>
</span></span><span class="line"><span class="cl"><span class="c1">#PID&lt;0.98.0&gt;</span>
</span></span><span class="line"><span class="cl"><span class="n">iex</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span><span class="o">&gt;</span> <span class="n">pid</span> <span class="o">=</span> <span class="n">spawn</span> <span class="n">fn</span> <span class="o">-&gt;</span> <span class="no">MyProcess</span><span class="o">.</span><span class="n">start</span> <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="c1">#PID&lt;0.105.0&gt;</span>
</span></span><span class="line"><span class="cl"><span class="n">iex</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span><span class="o">&gt;</span> <span class="no">Process</span><span class="o">.</span><span class="n">alive?</span><span class="p">(</span><span class="n">pid</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="kp">true</span>
</span></span><span class="line"><span class="cl"><span class="n">iex</span><span class="p">(</span><span class="mi">4</span><span class="p">)</span><span class="o">&gt;</span> <span class="no">Process</span><span class="o">.</span><span class="n">link</span><span class="p">(</span><span class="n">pid</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="kp">true</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Com <tt>self</tt> conseguimos ver que o id do processo atual do shell IEx é &ldquo;0.98.0&rdquo;. Aí damos spawn num processo que chama o <tt>MyProcess.start/0</tt> de novo, ele vai bloquear no receive. Esse processo novo tem um id diferente, &ldquo;0.105.0&rdquo;.</p>
<p>Podemos confirmar que o novo processo está vivo e linkar o shell IEx com o pid &ldquo;0.105.0&rdquo;. Agora, qualquer coisa que aconteça com esse processo vai cascatear pro shell.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">iex</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span><span class="o">&gt;</span> <span class="no">Process</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="n">pid</span><span class="p">,</span> <span class="ss">:kill</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="o">**</span> <span class="p">(</span><span class="no">EXIT</span> <span class="n">from</span> <span class="c1">#PID&lt;0.98.0&gt;) killed</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="no">Interactive</span> <span class="no">Elixir</span> <span class="p">(</span><span class="mi">1</span><span class="o">.</span><span class="mi">1</span><span class="o">.</span><span class="mi">1</span><span class="p">)</span> <span class="o">-</span> <span class="n">press</span> <span class="no">Ctrl</span><span class="o">+</span><span class="n">C</span> <span class="n">to</span> <span class="nb">exit</span> <span class="p">(</span><span class="n">type</span> <span class="n">h</span><span class="p">()</span> <span class="no">ENTER</span> <span class="k">for</span> <span class="n">help</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="sr">/home/</span><span class="n">akitaonrails</span><span class="o">/.</span><span class="n">iex</span><span class="o">.</span><span class="n">exs</span><span class="p">:</span><span class="mi">1</span><span class="p">:</span> <span class="ss">warning</span><span class="p">:</span> <span class="n">redefining</span> <span class="k">module</span> <span class="nn">R</span>
</span></span><span class="line"><span class="cl"><span class="n">iex</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">&gt;</span> <span class="nb">self</span>
</span></span><span class="line"><span class="cl"><span class="c1">#PID&lt;0.109.0&gt;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E de fato, se mandarmos uma mensagem de kill à força pro processo &ldquo;0.105.0&rdquo;, o shell IEx também é morto no processo. O IEx reinicia e seu novo pid é &ldquo;0.109.0&rdquo; no lugar do antigo &ldquo;0.98.0&rdquo;. A propósito, essa é uma maneira em que um processo difere de um objeto comum. Ele se comporta mais como um processo de sistema operacional, onde um crash em um processo não afeta o sistema inteiro, já que não segura estado externo compartilhado que possa corromper o estado do sistema.</p>
<p>O conceito importante é que agora temos um mecanismo pra definir um Processo Pai (o IEx, neste exemplo) e Processos Filhos linkados a ele.</p>
<ol start="6">
<li>Processos pais não precisam suicidar-se de forma idiota só porque os filhos pisaram na bola. Em vez disso, eles podem capturar exits e decidir o que fazer depois:</li>
</ol>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">iex</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span><span class="o">&gt;</span> <span class="no">Process</span><span class="o">.</span><span class="n">flag</span><span class="p">(</span><span class="ss">:trap_exit</span><span class="p">,</span> <span class="kp">true</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="kp">false</span>
</span></span><span class="line"><span class="cl"><span class="n">iex</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span><span class="o">&gt;</span> <span class="n">pid</span> <span class="o">=</span> <span class="n">spawn_link</span> <span class="n">fn</span> <span class="o">-&gt;</span> <span class="no">MyProcess</span><span class="o">.</span><span class="n">start</span> <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="c1">#PID&lt;0.118.0&gt;</span>
</span></span><span class="line"><span class="cl"><span class="n">iex</span><span class="p">(</span><span class="mi">4</span><span class="p">)</span><span class="o">&gt;</span> <span class="nb">send</span> <span class="n">pid</span><span class="p">,</span> <span class="p">{</span><span class="ss">:counter</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="no">New</span> <span class="n">state</span> <span class="n">is</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span><span class="ss">:counter</span><span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Primeiro declaramos que o shell IEx vai capturar exits e não simplesmente morrer. Aí damos spawn num novo processo e o linkamos. A função <tt>spawn_link/1</tt> tem o mesmo efeito de fazer <tt>spawn/1</tt> seguido de <tt>Process.link/1</tt>. Mandamos uma mensagem pro novo pid e confirmamos que ele continua funcionando.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">iex</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span><span class="o">&gt;</span> <span class="no">Process</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="n">pid</span><span class="p">,</span> <span class="ss">:kill</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="kp">true</span>
</span></span><span class="line"><span class="cl"><span class="n">iex</span><span class="p">(</span><span class="mi">6</span><span class="p">)</span><span class="o">&gt;</span> <span class="no">Process</span><span class="o">.</span><span class="n">alive?</span><span class="p">(</span><span class="n">pid</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="kp">false</span>
</span></span><span class="line"><span class="cl"><span class="n">iex</span><span class="p">(</span><span class="mi">7</span><span class="p">)</span><span class="o">&gt;</span> <span class="n">flush</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span><span class="ss">:EXIT</span><span class="p">,</span> <span class="c1">#PID&lt;0.118.0&gt;, :killed}</span>
</span></span><span class="line"><span class="cl"><span class="ss">:ok</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora forçamos o kill no novo processo de novo, mas o IEx não trava dessa vez, porque está explicitamente capturando esses erros. Se checarmos o pid morto, confirmamos que ele realmente está morto. E agora também podemos inspecionar a caixa de mensagens do próprio processo do IEx (no caso, só dando flush no que está enfileirado no inbox) e ver que ele acabou de receber uma mensagem dizendo que seu filho foi morto.</p>
<p>A partir daí podemos fazer o processo do IEx tratar essa mensagem e decidir só lamentar a morte do seu filho falecido e suicidar-se também, ou seguir em frente e dar spawn_link num novo. Temos <strong>escolha</strong> em meio ao desastre.</p>
<h3>OTP Workers<span class="hx:absolute hx:-mt-20" id="otp-workers"></span>
    <a href="#otp-workers" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Deixando a metáfora macabra de lado, aprendemos que temos processos Pai e Filho, mas, mais importante, eles podem assumir os papéis de Supervisores e Workers supervisionados, respectivamente.</p>
<p>Workers é onde colocamos nosso código. Esse código pode ter bugs, pode depender de coisas externas que podem fazer nosso código crashar por motivos inesperados. Numa linguagem normal a gente começaria a usar os temidos blocos <tt>try/catch</tt>, que são feios e <strong>errados</strong>! Não capture erros em Elixir, deixe quebrar!!</p>
<p>Como expliquei no meu artigo anterior, tudo em Elixir acaba sendo o que se chama de &ldquo;OTP application&rdquo;. O exemplo acima é só uma engenhoca bem simples que podemos expandir. Vamos reescrever a mesma coisa como um OTP GenServer:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">MyFancyProcess</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">GenServer</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">start_link</span><span class="p">(</span><span class="n">_</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="no">GenServer</span><span class="o">.</span><span class="n">start_link</span><span class="p">(</span><span class="n">__MODULE__</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nb">name</span><span class="p">:</span> <span class="n">__MODULE__</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">## Public API</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">hello</span><span class="p">(</span><span class="n">message</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="no">GenServer</span><span class="o">.</span><span class="n">call</span><span class="p">(</span><span class="n">__MODULE__</span><span class="p">,</span> <span class="p">{</span><span class="ss">:hello</span><span class="p">,</span> <span class="n">message</span><span class="p">})</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">counter</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="no">GenServer</span><span class="o">.</span><span class="n">call</span><span class="p">(</span><span class="n">__MODULE__</span><span class="p">,</span> <span class="ss">:counter</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">## GenServer callbacks</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">init</span><span class="p">(</span><span class="n">start_counter</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">start_counter</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">handle_call</span><span class="p">({</span><span class="ss">:hello</span><span class="p">,</span> <span class="n">message</span><span class="p">},</span> <span class="n">_from</span><span class="p">,</span> <span class="n">state</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="no">IO</span><span class="o">.</span><span class="n">puts</span> <span class="s2">&#34;Hello, </span><span class="si">#{</span><span class="n">message</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="ss">:reply</span><span class="p">,</span> <span class="ss">:noproc</span><span class="p">,</span> <span class="n">state</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">handle_call</span><span class="p">(</span><span class="ss">:counter</span><span class="p">,</span> <span class="n">_from</span><span class="p">,</span> <span class="n">state</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">new_state</span> <span class="o">=</span> <span class="n">state</span> <span class="o">+</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">    <span class="no">IO</span><span class="o">.</span><span class="n">puts</span> <span class="s2">&#34;New state is </span><span class="si">#{</span><span class="n">new_state</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="ss">:reply</span><span class="p">,</span> <span class="ss">:noproc</span><span class="p">,</span> <span class="n">new_state</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Esse novo <tt>MyFancyProcess</tt> é essencialmente o mesmo que o <tt>MyProcess</tt>, só que com OTP GenServer por cima. Tem funções da Public API e callbacks do GenServer.</p>
<p>O livro do Benjamin se aprofunda em cada bit do que acabei de implementar. Mas por agora entenda algumas coisas básicas:</p>
<ol>
<li>
<p>O módulo faz &ldquo;<tt>use GenServer</tt>&rdquo; pra importar todas as partes necessárias do GenServer pra sua conveniência. Em essência, uma das coisas que ele faz é criar aquele bloco <tt>receive</tt> que fizemos na primeira versão pra esperar por mensagens.</p>
</li>
<li>
<p>A função <tt>start_link/1</tt> vai criar a instância desse GenServer e devolver o processo linkado. Internamente ela faz callback pra função <tt>init/1</tt> pra setar o estado inicial desse worker. Essa é uma linguagem flexível, temos várias maneiras de fazer a mesma coisa, e isso é bom; ter só uma única forma de escrever código é chato.</p>
</li>
<li>
<p>A convenção é ter uma função pública que chama o <tt>handle_call/3</tt> interno (pra chamadas síncronas), <tt>handle_cast/2</tt> (pra chamadas assíncronas), e <tt>handle_info/2</tt>. Você poderia simplesmente chamar <tt>handle_call</tt> direto de fora, mas é feio, então você vai encontrar essa convenção em todo lugar.</p>
</li>
</ol>
<p>Com isso no lugar, podemos começar a chamá-lo direto:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">iex</span><span class="p">(</span><span class="mi">11</span><span class="p">)</span><span class="o">&gt;</span> <span class="no">MyFancyProcess</span><span class="o">.</span><span class="n">start_link</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="c1">#PID&lt;0.261.0&gt;}</span>
</span></span><span class="line"><span class="cl"><span class="n">iex</span><span class="p">(</span><span class="mi">12</span><span class="p">)</span><span class="o">&gt;</span> <span class="no">MyFancyProcess</span><span class="o">.</span><span class="n">hello</span><span class="p">(</span><span class="s2">&#34;world&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="no">Hello</span><span class="p">,</span> <span class="n">world</span>
</span></span><span class="line"><span class="cl"><span class="ss">:noproc</span>
</span></span><span class="line"><span class="cl"><span class="n">iex</span><span class="p">(</span><span class="mi">13</span><span class="p">)</span><span class="o">&gt;</span> <span class="no">MyFancyProcess</span><span class="o">.</span><span class="n">counter</span>
</span></span><span class="line"><span class="cl"><span class="no">New</span> <span class="n">state</span> <span class="n">is</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl"><span class="ss">:noproc</span>
</span></span><span class="line"><span class="cl"><span class="n">iex</span><span class="p">(</span><span class="mi">14</span><span class="p">)</span><span class="o">&gt;</span> <span class="no">MyFancyProcess</span><span class="o">.</span><span class="n">counter</span>
</span></span><span class="line"><span class="cl"><span class="no">New</span> <span class="n">state</span> <span class="n">is</span> <span class="mi">2</span>
</span></span><span class="line"><span class="cl"><span class="ss">:noproc</span>
</span></span><span class="line"><span class="cl"><span class="n">iex</span><span class="p">(</span><span class="mi">15</span><span class="p">)</span><span class="o">&gt;</span> <span class="no">MyFancyProcess</span><span class="o">.</span><span class="n">counter</span>
</span></span><span class="line"><span class="cl"><span class="no">New</span> <span class="n">state</span> <span class="n">is</span> <span class="mi">3</span>
</span></span><span class="line"><span class="cl"><span class="ss">:noproc</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E isso é muito mais limpo do que a versão onde fizemos <tt>spawn_link</tt> manual e mandamos <tt>send</tt> de mensagens pra um pid. Tudo isso é tratado de forma elegante pelo GenServer por baixo. E como eu disse, os resultados são os mesmos do exemplo cru do <tt>MyProcess</tt>.</p>
<p>Na verdade, essa convenção faz a gente digitar muita boilerplate várias vezes. Existe uma biblioteca chamada <a href="https://github.com/sasa1977/exactor"target="_blank" rel="noopener">ExActor</a> que simplifica bastante uma implementação de GenServer, fazendo nosso código anterior virar algo assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">MyFancyProcess</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">ExActor</span><span class="o">.</span><span class="n">GenServer</span><span class="p">,</span> <span class="ss">initial_state</span><span class="p">:</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">defcall</span> <span class="n">hello</span><span class="p">(</span><span class="n">message</span><span class="p">),</span> <span class="n">state</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="no">IO</span><span class="o">.</span><span class="n">puts</span> <span class="s2">&#34;Hello, </span><span class="si">#{</span><span class="n">message</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">noreply</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">defcall</span> <span class="n">counter</span><span class="p">,</span> <span class="n">state</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">new_counter</span> <span class="o">=</span> <span class="n">state</span> <span class="o">+</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">    <span class="no">IO</span><span class="o">.</span><span class="n">puts</span> <span class="s2">&#34;New state is </span><span class="si">#{</span><span class="n">new_counter</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">new_state</span><span class="p">(</span><span class="n">new_counter</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Bem mais limpo, mas como estamos só usando o IEx, não vou usar essa versão na próxima seção. Fique com a versão mais longa do <tt>MyFancyProcess</tt> listada no começo desta seção!</p>
<h2>OTP Supervisor<span class="hx:absolute hx:-mt-20" id="otp-supervisor"></span>
    <a href="#otp-supervisor" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Agora que temos um worker, podemos criar um Supervisor pra supervisioná-lo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">MyFancySupervisor</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">Supervisor</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">start_link</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="no">Supervisor</span><span class="o">.</span><span class="n">start_link</span><span class="p">(</span><span class="n">__MODULE__</span><span class="p">,</span> <span class="o">[]</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">init</span><span class="p">(</span><span class="n">_</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">children</span> <span class="o">=</span> <span class="o">[</span>
</span></span><span class="line"><span class="cl">      <span class="n">worker</span><span class="p">(</span><span class="no">MyFancyProcess</span><span class="p">,</span> <span class="o">[</span><span class="mi">0</span><span class="o">]</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">opts</span> <span class="o">=</span> <span class="o">[</span><span class="ss">strategy</span><span class="p">:</span> <span class="ss">:one_for_one</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">supervise</span><span class="p">(</span><span class="n">children</span><span class="p">,</span> <span class="n">opts</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Esse é só um boilerplate simples que a maioria dos Supervisors vai ter. Tem muitos detalhes que você precisa aprender, mas pra fins deste artigo as partes importantes são, primeiro, a definição da especificação dos <tt>children</tt>, dizendo que esse Supervisor deve iniciar o GenServer <tt>MyFancyProcess</tt> em vez de termos que fazer <tt>MyFancyProcess.start_link</tt> manualmente. E a segunda parte importante é a lista de <tt>opts</tt> que define a estratégia <tt>:one_for_one</tt>, ou seja, se o Supervisor detecta que o filho morreu, deve reiniciá-lo.</p>
<p>A partir de um IEx limpo, podemos copiar e colar tanto o <tt>MyFancyProcess</tt> quanto o <tt>MyFancySupervisor</tt> acima e começar a brincar com isso no shell IEx:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">iex</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span><span class="o">&gt;</span> <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">sup_pid</span><span class="p">}</span> <span class="o">=</span> <span class="no">MyFancySupervisor</span><span class="o">.</span><span class="n">start_link</span>   
</span></span><span class="line"><span class="cl"><span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="c1">#PID&lt;0.124.0&gt;}</span>
</span></span><span class="line"><span class="cl"><span class="n">iex</span><span class="p">(</span><span class="mi">4</span><span class="p">)</span><span class="o">&gt;</span> <span class="no">MyFancyProcess</span><span class="o">.</span><span class="n">hello</span><span class="p">(</span><span class="s2">&#34;foo&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="no">Hello</span><span class="p">,</span> <span class="n">foo</span>
</span></span><span class="line"><span class="cl"><span class="ss">:noproc</span>
</span></span><span class="line"><span class="cl"><span class="n">iex</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span><span class="o">&gt;</span> <span class="no">MyFancyProcess</span><span class="o">.</span><span class="n">counter</span>     
</span></span><span class="line"><span class="cl"><span class="no">New</span> <span class="n">state</span> <span class="n">is</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl"><span class="ss">:noproc</span>
</span></span><span class="line"><span class="cl"><span class="n">iex</span><span class="p">(</span><span class="mi">6</span><span class="p">)</span><span class="o">&gt;</span> <span class="no">MyFancyProcess</span><span class="o">.</span><span class="n">counter</span>
</span></span><span class="line"><span class="cl"><span class="no">New</span> <span class="n">state</span> <span class="n">is</span> <span class="mi">2</span>
</span></span><span class="line"><span class="cl"><span class="ss">:noproc</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>É assim que iniciamos o Supervisor e dá pra ver que de cara já podemos mandar mensagens pro GenServer <tt>MyFancyProcess</tt>, porque o Supervisor o iniciou pra gente com sucesso.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">iex</span><span class="p">(</span><span class="mi">7</span><span class="p">)</span><span class="o">&gt;</span> <span class="no">Supervisor</span><span class="o">.</span><span class="n">count_children</span><span class="p">(</span><span class="n">sup_pid</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="sx">%{active: 1, specs: 1, supervisors: 0, workers: 1}</span>
</span></span><span class="line"><span class="cl"><span class="n">iex</span><span class="p">(</span><span class="mi">8</span><span class="p">)</span><span class="o">&gt;</span> <span class="no">Supervisor</span><span class="o">.</span><span class="n">which_children</span><span class="p">(</span><span class="n">sup_pid</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="o">[</span><span class="p">{</span><span class="no">MyFancyProcess</span><span class="p">,</span> <span class="c1">#PID&lt;0.125.0&gt;, :worker, [MyFancyProcess]}]</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Usando o PID do Supervisor que capturamos quando o iniciamos, podemos pedir pra ele contar quantos filhos está monitorando (1, neste exemplo) e podemos pedir os detalhes de cada um também. Dá pra ver que o <tt>MyFancyProcess</tt> começou com o pid &ldquo;0.125.0&rdquo;.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">iex</span><span class="p">(</span><span class="mi">9</span><span class="p">)</span><span class="o">&gt;</span> <span class="o">[</span><span class="p">{</span><span class="n">_</span><span class="p">,</span> <span class="n">worker_pid</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">}</span><span class="o">]</span> <span class="o">=</span> <span class="no">Supervisor</span><span class="o">.</span><span class="n">which_children</span><span class="p">(</span><span class="n">sup_pid</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="o">[</span><span class="p">{</span><span class="no">MyFancyProcess</span><span class="p">,</span> <span class="c1">#PID&lt;0.125.0&gt;, :worker, [MyFancyProcess]}]</span>
</span></span><span class="line"><span class="cl"><span class="n">iex</span><span class="p">(</span><span class="mi">14</span><span class="p">)</span><span class="o">&gt;</span> <span class="no">Process</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="n">worker_pid</span><span class="p">,</span> <span class="ss">:kill</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="kp">true</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora, podemos pegar o pid do Worker e forçá-lo a crashar manualmente como fizemos antes. Devíamos estar ferrados, certo? Não:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>iex(15)&gt; Supervisor.which_children(sup_pid)                          
[{MyFancyProcess, #PID&lt;0.139.0&gt;, :worker, [MyFancyProcess]}]

iex(16)&gt; MyFancyProcess.counter
New state is 1
:noproc
iex(17)&gt; MyFancyProcess.counter
New state is 2
:noproc</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Se perguntarmos ao Supervisor de novo a lista de filhos, vamos ver que o velho processo &ldquo;0.125.0&rdquo; sumiu mesmo, mas um novo, &ldquo;0.139.0&rdquo;, foi spawnado em seu lugar pela estratégia <tt>:one_for_one</tt> do Supervisor que definimos antes.</p>
<p>Podemos continuar fazendo chamadas pro <tt>MyFancyProcess</tt>, mas você vai ver que o estado anterior foi perdido e ele recomeça do zero. Podemos adicionar gerenciamento de estado no GenServer usando vários storages persistentes diferentes, como o <a href="http://elixir-lang.org/getting-started/mix-otp/ets.html"target="_blank" rel="noopener">ETS</a> embutido (pense no ETS como um Memcache embutido), mas acho que você já pegou a ideia.</p>
<h3>Visualizando Processos Graficamente<span class="hx:absolute hx:-mt-20" id="visualizando-processos-graficamente"></span>
    <a href="#visualizando-processos-graficamente" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Esse artigo inteiro foi motivado por essa coisinha simples no livro do Benjamin: lá pelo final da página 139 do livro você terá construído um sistema de pool bem simples capaz de iniciar 5 processos no pool, guardados por um supervisor. E daí ele parte pra mostrar o Observer.</p>
<p>O Erlang tem uma ferramenta inspetora embutida chamada Observer. Você pode usar as funções built-in do Supervisor pra inspecionar processos como demonstrei antes, mas é bem mais legal ver isso visualmente. Assumindo que você instalou o Erlang Solutions corretamente, no Ubuntu você precisa:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>wget https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb &amp;&amp; sudo dpkg -i erlang-solutions_1.0_all.deb</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Só então você pode iniciar o observer direto do shell IEx assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>:observer.start</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E uma janela gráfica vai aparecer com algumas estatísticas pra começar.</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/530/big_observer-system.png" alt="Observer"  loading="lazy" /></p>
<p>Isso é <strong>muito</strong> poderoso porque você tem visibilidade e controle sobre todo o runtime do Erlang! Veja que essa janela de status mostra até &ldquo;uptime&rdquo;, é como um sistema UNIX: foi feito pra ficar de pé não importa o que aconteça. Os processos têm seu próprio garbage collector e todos se comportam direitinho dentro do sistema.</p>
<p>Você também pode acoplar um Observer remoto a runtimes Erlang remotos, caso esteja se perguntando. Agora dá pra pular pra aba Applications pra ver como o exercício &ldquo;Pooly&rdquo; fica com 5 filhos no pool:</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/527/big_observer-pooly.png" alt="Pooly"  loading="lazy" /></p>
<p>Como esses filhos são supervisionados com estratégias de restart corretas, podemos matar visualmente um deles, o que tem o pid rotulado como &ldquo;0.389.0&rdquo;:</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/528/big_observer-kill.png" alt="Kill process"  loading="lazy" /></p>
<p>E como o Observer mostra na hora, o Supervisor entrou em ação, deu spawn num novo filho e o adicionou ao pool, trazendo a contagem de volta pra 5:</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/529/big_observer-restart.png" alt="Respawn"  loading="lazy" /></p>
<p>É isso que <strong>Tolerância a Falhas</strong> com controles adequados significa usando OTP!</p>
<p>Com as partes que expliquei neste artigo você deve ter conceitos suficientes pra finalmente entender do que se trata todo esse alarde sobre alta confiabilidade do Erlang. Os conceitos básicos são bem simples, plugar sua aplicação no OTP também é molezinha; o que o OTP tem implementado por baixo dos panos é o que torna sua aplicação muito mais confiável.</p>
<p>Há diretrizes claras sobre como projetar sua aplicação. Quem supervisiona o quê. O que deve acontecer com o estado da aplicação se workers forem reiniciados? Como dividir responsabilidades entre diferentes grupos de Supervisor/Children?</p>
<p>Sua aplicação deveria parecer uma Árvore, uma <strong>Árvore de Supervisão</strong>, onde uma falha em uma folha não derruba os outros galhos e tudo sabe como se comportar e como se recuperar, com elegância. É realmente como um sistema operacional UNIX: quando você dá <tt>kill -9</tt> num processo, isso não derruba o sistema, e se for um serviço monitorado pelo <tt>initd</tt>, ele é respawnado.</p>
<p>Mais importante: isso não é uma feature opcional, uma biblioteca de terceiros, que você escolhe usar. É embutido no Erlang, você precisa usar se quiser jogar. Não há outra escolha e essa é a melhor escolha. Qualquer padrão desses que não esteja implementado numa linguagem concorrente, pra mim, representa uma grande falha da linguagem. Essa é a força do Elixir.</p>
<p>Esse é um nível de controle alto que você não vai encontrar em outro lugar. E ainda nem falamos sobre como aplicações OTP podem trocar mensagens pela rede em sistemas realmente distribuídos, e como o runtime do Erlang consegue recarregar código enquanto a aplicação está rodando, com zero downtime, parecido com o que o próprio IEx é capaz e como o Phoenix permite o modo de desenvolvimento com code reloading! O OTP dá tudo isso de graça, então vale muito a pena aprender todos os detalhes.</p>
<p>Passamos por processos, pids, mandar uma mensagem de kill pra um processo, capturar exits, processo pai tendo processos filhos. Parece bem similar ao funcionamento do UNIX. Se você conhece UNIX, fica fácil entender como tudo isso se encaixa, incluindo o operador pipe do Elixir &ldquo;|&gt;&rdquo; comparado ao próprio pipe &ldquo;|&rdquo; do UNIX, é parecido.</p>
<p>Pra fechar, o The Little Elixir &amp; OTP Guidebook é um livro bem fácil de ler, bem mão na massa, pequeno. Você consegue ler em alguns dias e absorver tudo que resumi rapidamente aqui e muito mais. Recomendo demais que você compre agora.</p>
]]></content:encoded><category>elixir-e-erlang</category><category>sistemas-distribuidos</category><category>engenharia-de-software</category></item><item><title>Ex Manga Downloadr - Parte 2: Poolboy ao resgate!</title><link>https://akitaonrails.github.io/2015/11/19/ex-manga-downloadr-parte-2-poolboy-ao-resgate/</link><guid isPermaLink="true">https://akitaonrails.github.io/2015/11/19/ex-manga-downloadr-parte-2-poolboy-ao-resgate/</guid><pubDate>Thu, 19 Nov 2015 14:35:00 -0200</pubDate><description>&lt;p&gt;Se você leu o &lt;a href="http://www.akitaonrails.com/2015/11/18/ex-manga-downloadr-um-exercicio-com-elixir"target="_blank" rel="noopener"&gt;meu artigo anterior&lt;/a&gt;, eu descrevi rapidamente meu exercício construindo um downloader pro MangaReader. Se ainda não leu, recomendo dar uma olhada antes de continuar.&lt;/p&gt;
&lt;p&gt;No meio do artigo, eu falei que ainda estava me coçando pra entender qual seria a melhor forma de lidar com várias requisições HTTP simultâneas contra uma fonte externa instável, onde eu não consigo controlar timeout nem outros problemas de rede.&lt;/p&gt;
&lt;p&gt;Um Mangá grande tipo Naruto ou Bleach tem dezenas de capítulos com dezenas de páginas cada, o que dá milhares de requisições HTTP necessárias. Elixir/Erlang me permitem disparar quantas requisições HTTP em paralelo eu quiser. Só que, fazendo isso, as requisições começam a dar timeout muito rápido (é praticamente um ataque distribuído de negação de serviço contra o MangaReader).&lt;/p&gt;</description><content:encoded><![CDATA[<p>Se você leu o <a href="http://www.akitaonrails.com/2015/11/18/ex-manga-downloadr-um-exercicio-com-elixir"target="_blank" rel="noopener">meu artigo anterior</a>, eu descrevi rapidamente meu exercício construindo um downloader pro MangaReader. Se ainda não leu, recomendo dar uma olhada antes de continuar.</p>
<p>No meio do artigo, eu falei que ainda estava me coçando pra entender qual seria a melhor forma de lidar com várias requisições HTTP simultâneas contra uma fonte externa instável, onde eu não consigo controlar timeout nem outros problemas de rede.</p>
<p>Um Mangá grande tipo Naruto ou Bleach tem dezenas de capítulos com dezenas de páginas cada, o que dá milhares de requisições HTTP necessárias. Elixir/Erlang me permitem disparar quantas requisições HTTP em paralelo eu quiser. Só que, fazendo isso, as requisições começam a dar timeout muito rápido (é praticamente um ataque distribuído de negação de serviço contra o MangaReader).</p>
<p>Por tentativa e erro descobri que disparar menos de 100 requisições HTTP de uma vez me permite terminar o trabalho. Eu travei em 80 só pra garantir, mas isso depende muito do seu ambiente.</p>
<p>Aí eu tinha que manualmente picar minha lista de páginas em pedaços de 80 elementos e processar em paralelo, depois reduzir as listas resultantes de volta numa lista maior pra repassar pros próximos passos do Workflow. O código fica meio enrolado assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">images_sources</span><span class="p">(</span><span class="n">pages_list</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">pages_list</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="n">chunk</span><span class="p">(</span><span class="vi">@maximum_fetches</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">reduce</span><span class="p">(</span><span class="o">[]</span><span class="p">,</span> <span class="n">fn</span> <span class="n">pages_chunk</span><span class="p">,</span> <span class="n">acc</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="n">result</span> <span class="o">=</span> <span class="n">pages_chunk</span>
</span></span><span class="line"><span class="cl">        <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="p">(</span><span class="no">Task</span><span class="o">.</span><span class="n">async</span><span class="p">(</span><span class="n">fn</span> <span class="o">-&gt;</span> <span class="no">Page</span><span class="o">.</span><span class="n">image</span><span class="p">(</span><span class="o">&amp;</span><span class="mi">1</span><span class="p">)</span> <span class="k">end</span><span class="p">)))</span>
</span></span><span class="line"><span class="cl">        <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="p">(</span><span class="no">Task</span><span class="o">.</span><span class="n">await</span><span class="p">(</span><span class="o">&amp;</span><span class="mi">1</span><span class="p">,</span> <span class="vi">@http_timeout</span><span class="p">)))</span>
</span></span><span class="line"><span class="cl">        <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">fn</span> <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">image</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="n">image</span> <span class="k">end</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="n">acc</span> <span class="o">++</span> <span class="n">result</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora consegui reimplementar essa parte e o mesmo código ficou assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">images_sources</span><span class="p">(</span><span class="n">pages_list</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">pages_list</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="no">Worker</span><span class="o">.</span><span class="n">page_image</span><span class="o">/</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="no">Task</span><span class="o">.</span><span class="n">await</span><span class="p">(</span><span class="o">&amp;</span><span class="mi">1</span><span class="p">,</span> <span class="vi">@await_timeout_ms</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">fn</span> <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">image</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="n">image</span> <span class="k">end</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Uau! Que melhora absurda, e ficou muito mais óbvio o que ele tá fazendo.</p>
<p>E o melhor: baixar um Mangá do tamanho de Akira (em torno de 2.200 páginas) levou menos de <strong>50 segundos</strong>. E não é porque Elixir é super rápido, é porque o MangaReader não aguenta o tranco se eu aumentar o tamanho do Pool. Ele tá batendo numa taxa constante de 50 conexões simultâneas!</p>
<p>Isso faz minha máquina de 4 cores, sentada numa conexão de 40Mbs, usar menos de 40% ~ 30% de CPU e não mais que uns 3,5 Mbs. Se o MangaReader aguentasse o tranco, daria pra puxar todas as páginas 2 ou 3 vezes mais rápido sem suar a camisa.</p>
<p>Já era rápido com a estratégia anterior, mas acho que ficou pelo menos duas vezes mais rápido como bônus. E como foi que eu consegui isso?</p>
<h3>Open Telecom Platform<span class="hx:absolute hx:-mt-20" id="open-telecom-platform"></span>
    <a href="#open-telecom-platform" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>No artigo anterior eu também disse que ainda não queria mergulhar em &ldquo;OTP e GenServers&rdquo;. Mas, se você é iniciante igual eu, provavelmente ainda nem entendeu direito o que isso quer dizer.</p>
<p>OTP é o que torna Erlang (e por consequência, Elixir) diferente de praticamente qualquer outra plataforma de linguagem, com a possível exceção de Java.</p>
<p>Muitas linguagens novas hoje em dia tentam fazer várias tarefas em paralelo através de padrões Reactor enrolados (Node.js, EventMachine/Ruby, Tornado/Twisted/Python, etc) ou através de green threads (mais limpas) como em Scala e Go.</p>
<p>Mas nada disso importa muito. Lançar milhões de processos leves até é fácil, o difícil é <strong>CONTROLAR</strong> todos eles. Não adianta nada conseguir esgotar seu hardware se você não consegue controlá-lo. Você acaba com milhões de minions burros causando o caos sem nenhum adulto pra coordenar.</p>
<p>Erlang resolveu esse problema décadas atrás através do desenvolvimento do OTP, inicialmente chamado de Open Systems, dentro da Ericsson em 1995. Por si só, OTP é um assunto que tranquilamente preenche um livro gordo inteiro e mesmo assim você ainda não vai conseguir se chamar de expert.</p>
<p>Só pra eu não ficar chato demais aqui:</p>
<ul>
<li>Comece com <a href="http://learnyousomeerlang.com/what-is-otp"target="_blank" rel="noopener">esse resumo bem curtinho</a>;</li>
<li>Depois leia o <a href="http://elixir-lang.org/getting-started/mix-otp/supervisor-and-application.html"target="_blank" rel="noopener">tutorial de Supervisores do Elixir</a> e a página sobre <a href="http://elixir-lang.org/getting-started/processes.html"target="_blank" rel="noopener">Processos</a>, se ainda não tiver lido;</li>
<li>Por fim, dá pra ir mais fundo com <a href="https://www.manning.com/books/the-little-elixir-and-otp-guidebook"target="_blank" rel="noopener">The Elixir &amp; OTP Guidebook</a> da Manning Publications.</li>
</ul>
<p>Agora, segue meu ponto de vista pessoal. Como eu também sou iniciante, me avisem nos comentários abaixo se eu peguei algum conceito errado.</p>
<p>OTP é uma coleção de tecnologias e frameworks. A parte que mais nos interessa é entender que isso é uma coleção sofisticada de padrões pra alcançar o Nirvana de sistemas distribuídos altamente confiáveis e altamente escaláveis. Sabe? Aquilo que toda nova plataforma promete pra você e não entrega.</p>
<p>Pros nossos propósitos bem simples, vamos retomar o que eu disse antes: é trivial disparar milhões de pequenos processos. Chamamos eles de &ldquo;workers&rdquo;. O OTP fornece os meios pra controlar esses workers: os Supervisores. E também fornece o conceito de Árvores de Supervisores (Supervisores que supervisionam outros Supervisores). Esse é o ponto central.</p>
<p>Os Supervisores são responsáveis por iniciar os workers e também por se recuperar de exceções vindas dos workers (é por isso que em Erlang/Elixir a gente não fica fazendo aquela coisa feia de try/catch: deixe o erro acontecer e ser capturado pelo Supervisor). Aí dá pra configurar o Supervisor pra lidar com um worker problemático, por exemplo, reiniciando-o.</p>
<p>A gente já tinha tocado nessa coisa de OTP antes. Uma <a href="https://github.com/elixir-lang/elixir/blob/master/lib/elixir/lib/task.ex"target="_blank" rel="noopener">Task</a> do Elixir é só uma abstração de alto nível. Internamente, ela inicia o próprio <a href="https://github.com/elixir-lang/elixir/blob/master/lib/elixir/lib/task/supervisor.ex"target="_blank" rel="noopener">supervisor</a> pra monitorar tarefas assíncronas.</p>
<p>Tem tantos assuntos e detalhes que fica difícil até começar. Um conceito importante de saber é sobre <strong>estado</strong>. Não existe estado global! (Eba, nada de pesadelo de globais do Javascript.) Cada função tem o próprio estado e pronto. Sem aquele conceito de um &ldquo;objeto&rdquo; que segura estado e métodos que modificam esse estado.</p>
<p>Mas existe sim o conceito de processos do Erlang. Um processo tem estado, é um pedacinho leve de estado que existe só em runtime. Pra executar uma função num processo separado e paralelo, dá pra fazer só isso:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">iex</span><span class="o">&gt;</span> <span class="n">spawn</span> <span class="n">fn</span> <span class="o">-&gt;</span> <span class="mi">1</span> <span class="o">+</span> <span class="mi">2</span> <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="c1">#PID&lt;0.43.0&gt;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Diferente de um objeto, um processo não tem um conjunto de métodos que acessam seus estados internos &ldquo;this&rdquo; ou &ldquo;self&rdquo;. Em vez disso, cada processo tem uma <strong>caixa de mensagens</strong> (mailbox). Quando você inicia (ou faz &ldquo;spawn&rdquo;, no jargão do Erlang) um novo processo, ele retorna um pid (process ID). A partir daí dá pra mandar mensagens pro processo através do pid. Cada processo tem sua mailbox e você pode escolher responder às mensagens que chegam e mandar respostas de volta pro pid que enviou. É assim que dá pra mandar uma mensagem pro console do IEx e receber as mensagens na mailbox dele:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">iex</span><span class="o">&gt;</span> <span class="nb">send</span> <span class="nb">self</span><span class="p">(),</span> <span class="p">{</span><span class="ss">:hello</span><span class="p">,</span> <span class="s2">&#34;world&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span><span class="ss">:hello</span><span class="p">,</span> <span class="s2">&#34;world&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="n">iex</span><span class="o">&gt;</span> <span class="n">receive</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl"><span class="o">...&gt;</span>   <span class="p">{</span><span class="ss">:hello</span><span class="p">,</span> <span class="n">msg</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="n">msg</span>
</span></span><span class="line"><span class="cl"><span class="o">...&gt;</span>   <span class="p">{</span><span class="ss">:world</span><span class="p">,</span> <span class="n">msg</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="s2">&#34;won&#39;t match&#34;</span>
</span></span><span class="line"><span class="cl"><span class="o">...&gt;</span> <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="s2">&#34;world&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Em essência, é quase como um &ldquo;objeto&rdquo; que segura estado. Cada processo tem seu próprio garbage collector, então quando ele morre é coletado individualmente. E cada processo é isolado dos outros, eles não vazam estado entre si, o que torna tudo bem mais fácil de raciocinar.</p>
<p>A <a href="http://elixir-lang.org/getting-started/processes.html"target="_blank" rel="noopener">página de Getting Started sobre Processos</a> do site do Elixir mostra exemplos do que acabei de explicar e recomendo muito que você acompanhe direitinho.</p>
<p>Resumindo, um processo pode segurar estado interno travando indefinidamente esperando uma mensagem chegar na mailbox e depois recursando em si mesmo! Esse é um conceito que explode a cabeça à primeira vista.</p>
<p>Mas um processo simples sozinho é fraco demais. É aí que entra o <a href="http://elixir-lang.org/getting-started/mix-otp/genserver.html"target="_blank" rel="noopener">GenServer do OTP</a>, que é um processo bem mais completo. O OTP expõe Behaviours pra você implementar com seu próprio código, enquanto ele cuida da parte chata da infraestrutura pra você não ter que se preocupar com isso.</p>
<h3>Delegando as Chamadas Pesadas do Workflow pra um Worker<span class="hx:absolute hx:-mt-20" id="delegando-as-chamadas-pesadas-do-workflow-pra-um-worker"></span>
    <a href="#delegando-as-chamadas-pesadas-do-workflow-pra-um-worker" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Dito tudo isso, sabemos que no Workflow que implementamos antes a gente tem problema com as funções <tt>Page.image/1</tt> e <tt>Workflow.download_image/2</tt>. É por isso que tornamos elas processos assíncronos e esperamos por lotes de 80 chamadas a cada vez.</p>
<p>Agora, vamos começar movendo essa lógica pra um Worker GenServer, por exemplo, no arquivo <tt>ex_manga_downloadr/pool_management/worker.ex</tt>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">PoolManagement</span><span class="o">.</span><span class="n">Worker</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">GenServer</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">MangaReader</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="vi">@timeout_ms</span> <span class="mi">1_000_000</span>
</span></span><span class="line"><span class="cl">  <span class="vi">@transaction_timeout_ms</span> <span class="mi">1_000_000</span> <span class="c1"># maior só por garantia</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">start_link</span><span class="p">(</span><span class="n">_</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="no">GenServer</span><span class="o">.</span><span class="n">start_link</span><span class="p">(</span><span class="n">__MODULE__</span><span class="p">,</span> <span class="kp">nil</span><span class="p">,</span> <span class="o">[]</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">handle_call</span><span class="p">({</span><span class="ss">:page_image</span><span class="p">,</span> <span class="n">page_link</span><span class="p">},</span> <span class="n">_from</span><span class="p">,</span> <span class="n">state</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="ss">:reply</span><span class="p">,</span> <span class="no">Page</span><span class="o">.</span><span class="n">image</span><span class="p">(</span><span class="n">page_link</span><span class="p">),</span> <span class="n">state</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">handle_call</span><span class="p">({</span><span class="ss">:page_download_image</span><span class="p">,</span> <span class="n">image_data</span><span class="p">,</span> <span class="n">directory</span><span class="p">},</span> <span class="n">_from</span><span class="p">,</span> <span class="n">state</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="ss">:reply</span><span class="p">,</span> <span class="no">Page</span><span class="o">.</span><span class="n">download_image</span><span class="p">(</span><span class="n">image_data</span><span class="p">,</span> <span class="n">directory</span><span class="p">),</span> <span class="n">state</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Primeiro movi o <tt>Workflow.download_image/2</tt> pra <tt>Page.download_image/2</tt> só por consistência. Mas esse é um GenServer em poucas palavras. Temos uma configuração inicial na função <tt>start_link/1</tt> e implementamos funções <tt>handle_call/3</tt> pra lidar com cada tipo de argumento que ele pode receber. A separação acontece através de pattern matching dos argumentos.</p>
<p>Por convenção, dá pra adicionar funções públicas que são versões mais bonitinhas que chamam cada <tt>handle_call/3</tt>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">page_image</span><span class="p">(</span><span class="n">page_link</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="no">Task</span><span class="o">.</span><span class="n">async</span> <span class="n">fn</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="ss">:poolboy</span><span class="o">.</span><span class="n">transaction</span> <span class="ss">:worker_pool</span><span class="p">,</span> <span class="n">fn</span><span class="p">(</span><span class="n">server</span><span class="p">)</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="no">GenServer</span><span class="o">.</span><span class="n">call</span><span class="p">(</span><span class="n">server</span><span class="p">,</span> <span class="p">{</span><span class="ss">:page_image</span><span class="p">,</span> <span class="n">page_link</span><span class="p">},</span> <span class="vi">@timeout_ms</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="k">end</span><span class="p">,</span> <span class="vi">@transaction_timeout_ms</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">page_download_image</span><span class="p">(</span><span class="n">image_data</span><span class="p">,</span> <span class="n">directory</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="no">Task</span><span class="o">.</span><span class="n">async</span> <span class="n">fn</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="ss">:poolboy</span><span class="o">.</span><span class="n">transaction</span> <span class="ss">:worker_pool</span><span class="p">,</span> <span class="n">fn</span><span class="p">(</span><span class="n">server</span><span class="p">)</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="no">GenServer</span><span class="o">.</span><span class="n">call</span><span class="p">(</span><span class="n">server</span><span class="p">,</span> <span class="p">{</span><span class="ss">:page_download_image</span><span class="p">,</span> <span class="n">image_data</span><span class="p">,</span> <span class="n">directory</span><span class="p">},</span> <span class="vi">@timeout_ms</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="k">end</span><span class="p">,</span> <span class="vi">@transaction_timeout_ms</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Mas a gente não está só chamando os <tt>handle_call/3</tt> anteriores. Primeiro temos o <tt>Task.async/1</tt> que já estávamos usando nas funções do Workflow pra fazer os lotes paralelos. Mas dentro das chamadas Task tem essa outra coisa estranha: <tt>:poolboy</tt>.</p>
<h3>Controlando Pools de Processos com Poolboy<span class="hx:absolute hx:-mt-20" id="controlando-pools-de-processos-com-poolboy"></span>
    <a href="#controlando-pools-de-processos-com-poolboy" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Toda essa enrolação sobre OTP que escrevi aqui foi só uma introdução pra eu poder mostrar o <a href="https://github.com/devinus/poolboy"target="_blank" rel="noopener">Poolboy</a>.</p>
<p>Repetindo de novo: é trivial disparar milhões de processos. OTP é como controlamos as falhas desses processos. Mas tem outro problema: a computação dentro de cada processo pode ser tão pesada que a gente acaba derrubando a máquina ou, no nosso caso, fazendo um Distributed Denial of Service (DDoS) contra o pobre site do MangaReader.</p>
<p>Minha ideia inicial foi só fazer requisições paralelas em lotes. Mas a lógica fica enrolada.</p>
<p>Em vez disso, podemos usar um pool de processos! Ele enfileira nossas requisições por novos processos. Sempre que um processo termina, ele é devolvido pro pool e uma nova computação pode tomar o processo disponível. É assim que pools funcionam (você provavelmente tem uma intuição de como funciona pelos pools tradicionais de conexão de banco de dados). Pools e filas são construções de software úteis pra lidar com recursos limitados.</p>
<p>Fazendo isso, dá pra remover o picotamento da lista grande em lotes e processar como se fôssemos atacar cada elemento da lista grande em paralelo de uma só vez, repetindo de novo a versão inicial:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">pages_list</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="n">chunk</span><span class="p">(</span><span class="vi">@maximum_fetches</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">reduce</span><span class="p">(</span><span class="o">[]</span><span class="p">,</span> <span class="n">fn</span> <span class="n">pages_chunk</span><span class="p">,</span> <span class="n">acc</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">pages_chunk</span>
</span></span><span class="line"><span class="cl">      <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="p">(</span><span class="no">Task</span><span class="o">.</span><span class="n">async</span><span class="p">(</span><span class="n">fn</span> <span class="o">-&gt;</span> <span class="no">Page</span><span class="o">.</span><span class="n">image</span><span class="p">(</span><span class="o">&amp;</span><span class="mi">1</span><span class="p">)</span> <span class="k">end</span><span class="p">)))</span>
</span></span><span class="line"><span class="cl">      <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="p">(</span><span class="no">Task</span><span class="o">.</span><span class="n">await</span><span class="p">(</span><span class="o">&amp;</span><span class="mi">1</span><span class="p">,</span> <span class="vi">@http_timeout</span><span class="p">)))</span>
</span></span><span class="line"><span class="cl">      <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">fn</span> <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">image</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="n">image</span> <span class="k">end</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">acc</span> <span class="o">++</span> <span class="n">result</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora, removendo o chunking e a lógica de redução:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">pages_list</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="p">(</span><span class="no">Task</span><span class="o">.</span><span class="n">async</span><span class="p">(</span><span class="n">fn</span> <span class="o">-&gt;</span> <span class="no">Page</span><span class="o">.</span><span class="n">image</span><span class="p">(</span><span class="o">&amp;</span><span class="mi">1</span><span class="p">)</span> <span class="k">end</span><span class="p">)))</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="p">(</span><span class="no">Task</span><span class="o">.</span><span class="n">await</span><span class="p">(</span><span class="o">&amp;</span><span class="mi">1</span><span class="p">,</span> <span class="vi">@http_timeout</span><span class="p">)))</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">fn</span> <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">image</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="n">image</span> <span class="k">end</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E finalmente, substituindo a chamada direta de <tt>Task.async/1</tt> pelo worker do GenServer que acabamos de implementar:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">pages_list</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="no">Worker</span><span class="o">.</span><span class="n">page_image</span><span class="o">/</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="no">Task</span><span class="o">.</span><span class="n">await</span><span class="p">(</span><span class="o">&amp;</span><span class="mi">1</span><span class="p">,</span> <span class="vi">@await_timeout_ms</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">fn</span> <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">image</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="n">image</span> <span class="k">end</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora, o Poolboy vai exigir um Supervisor que monitore nosso Worker. Vamos colocar ele em <tt>ex_manga_downloadr/pool_management/supervisor.ex</tt>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">PoolManagement</span><span class="o">.</span><span class="n">Supervisor</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">Supervisor</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">start_link</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="no">Supervisor</span><span class="o">.</span><span class="n">start_link</span><span class="p">(</span><span class="n">__MODULE__</span><span class="p">,</span> <span class="o">[]</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">init</span><span class="p">(</span><span class="o">[]</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">pool_options</span> <span class="o">=</span> <span class="o">[</span>
</span></span><span class="line"><span class="cl">      <span class="nb">name</span><span class="p">:</span> <span class="p">{</span><span class="ss">:local</span><span class="p">,</span> <span class="ss">:worker_pool</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="ss">worker_module</span><span class="p">:</span> <span class="no">PoolManagement</span><span class="o">.</span><span class="n">Worker</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="ss">size</span><span class="p">:</span> <span class="mi">50</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="ss">max_overflow</span><span class="p">:</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">    <span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">children</span> <span class="o">=</span> <span class="o">[</span>
</span></span><span class="line"><span class="cl">      <span class="ss">:poolboy</span><span class="o">.</span><span class="n">child_spec</span><span class="p">(</span><span class="ss">:worker_pool</span><span class="p">,</span> <span class="n">pool_options</span><span class="p">,</span> <span class="o">[]</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">supervise</span><span class="p">(</span><span class="n">children</span><span class="p">,</span> <span class="ss">strategy</span><span class="p">:</span> <span class="ss">:one_for_one</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Mais bondades do OTP por aqui. Tínhamos um Worker descontrolado, agora temos um Supervisor responsável delegando a responsabilidade pro Poolboy. Começamos com um pool que comporta no máximo 50 processos dentro (sem overflow). Esse número também veio de tentativa e erro. E o Supervisor vai usar uma estratégia <tt>:one_for_one</tt>, o que quer dizer que se o Worker morrer, ele reinicia o coitado.</p>
<p>Agora, precisamos adicionar o Poolboy no <tt>mix.exs</tt> como dependência e rodar <tt>mix deps.get</tt> pra baixar:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defp</span> <span class="n">deps</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="o">[</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="ss">:poolboy</span><span class="p">,</span> <span class="ss">github</span><span class="p">:</span> <span class="s2">&#34;devinus/poolboy&#34;</span><span class="p">,</span> <span class="ss">tag</span><span class="p">:</span> <span class="s2">&#34;1.5.1&#34;</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>No mesmo <tt>mix.exs</tt>, fazemos a Application principal (surpresa: que <a href="https://github.com/elixir-lang/elixir/blob/master/lib/elixir/lib/application.ex"target="_blank" rel="noopener">já é uma aplicação OTP supervisionada</a>) iniciar o PoolManagement.Supervisor pra gente:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">application</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="o">[</span><span class="ss">applications</span><span class="p">:</span> <span class="o">[</span><span class="ss">:logger</span><span class="p">,</span> <span class="ss">:httpotion</span><span class="p">,</span> <span class="ss">:porcelain</span><span class="o">]</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="ss">mod</span><span class="p">:</span> <span class="p">{</span><span class="no">PoolManagement</span><span class="p">,</span> <span class="o">[]</span><span class="p">}</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Mas também precisamos ter esse módulo <tt>PoolManagement</tt> pra ser chamado. Podemos chamá-lo de <tt>pool_management.ex</tt>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">PoolManagement</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">Application</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">start</span><span class="p">(</span><span class="n">_type</span><span class="p">,</span> <span class="n">_args</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="no">PoolManagement</span><span class="o">.</span><span class="n">Supervisor</span><span class="o">.</span><span class="n">start_link</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<h3>Resumo<span class="hx:absolute hx:-mt-20" id="resumo"></span>
    <a href="#resumo" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Vamos resumir:</p>
<ol>
<li>a aplicação <tt>ExMangaDownloadr</tt> sobe e dispara a aplicação <tt>PoolManagement</tt>;</li>
<li>a aplicação <tt>PoolManagement</tt> dispara o <tt>PoolManagement.Supervisor</tt>;</li>
<li>o <tt>PoolManagement.Supervisor</tt> dispara o Poolboy e atribui o <tt>PoolManagement.Worker</tt> pra ele, configurando o tamanho do pool em 50 e respondendo pelo nome de pool <tt>:worker_pool</tt>;</li>
<li>agora começamos a buscar e fazer parsing dos capítulos e páginas do Mangá até que <tt>ExMangaDownloadr.Workflow.images_sources/1</tt> é chamado;</li>
<li>ele vai chamar a função <tt>PoolManagement.Worker.page_image/1</tt> que por sua vez dispara um <tt>Task.async/1</tt>, chamando <tt>:poolboy.transaction(:worker_pool, fn -&gt; &hellip; end)</tt>;</li>
<li>se tem um processo disponível no pool do Poolboy, ele inicia na hora, senão fica aguardando até que um processo fique disponível, esperando até o timeout configurado em <tt>@transaction_timeout_ms</tt>.</li>
<li>o processo mapeia toda a <tt>pages_list</tt>, criando uma Task assíncrona pra cada página da lista, terminamos com uma lista gigantesca de pids de Task, e aí fazemos <tt>Task.await/2</tt> pra todas elas retornarem.</li>
</ol>
<p>Agora, essa aplicação ficou bem mais confiável e mais rápida, já que ela dispara uma nova conexão HTTP assim que a primeira responde e devolve o processo pro pool. Em vez de disparar 80 conexões de uma vez, em lotes, começamos com 50 ao mesmo tempo e depois disparamos uma de cada vez pra cada processo devolvido ao pool.</p>
<p>Por tentativa e erro deixei o <tt>@http_timeout</tt> esperando no máximo 60 segundos. Também coloquei o <tt>timeout_ms</tt>, que é o tempo de espera pra chamada handle do worker GenServer retornar, e o <tt>transaction_timeout_ms</tt>, que é o tempo que o Poolboy aguarda por um novo processo no pool, ambos em torno de 16 minutos (1.000.000 ms).</p>
<p>É colocar 25 anos de experiência da Ericsson no setor de Telecom pra trabalhar a nosso favor!</p>
<p>E pra deixar bem claro: OTP é a coisa que separa Erlang/Elixir de todo o resto. Não é a mesma coisa, mas é como se o padrão fosse escrever tudo em Java como um EJB, pronto pra rodar dentro de um Container JEE. O que vem à mente é: pesado.</p>
<p>Em Erlang, uma aplicação OTP é leve, dá pra simplesmente construir e usar ad hoc, sem burocracia, sem ter que configurar servidores complicados. Como no nosso caso, é uma ferramenta de linha de comando bem simples e, dentro dela, todo o poder de um Container JEE! Pensa comigo.</p>
]]></content:encoded><category>elixir-e-erlang</category><category>performance</category><category>sistemas-distribuidos</category></item></channel></rss>