<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Elixir-E-Erlang | AkitaOnRails.com</title><link>https://akitaonrails.github.io/tags/elixir-e-erlang/</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:48 +0000</lastBuildDate><atom:link href="https://akitaonrails.github.io/tags/elixir-e-erlang/index.xml" rel="self" type="application/rss+xml"/><item><title>Existem Linguagens Melhores pra programar com LLMs?</title><link>https://akitaonrails.github.io/2026/10/11/existem-linguagens-melhores-pra-programar-com-llms/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/10/11/existem-linguagens-melhores-pra-programar-com-llms/</guid><pubDate>Sun, 11 Oct 2026 21:00:00 -0300</pubDate><description>&lt;p&gt;Semana passada o DHH resolveu farmar engajamento com o formato mais preguiçoso que existe: converteu o Campfire, o app de chat dele, pra várias linguagens usando agentes de IA, e foi soltando os resultados de throughput um a um no X. Na série que ele postou, a versão em Rust saiu na frente das equivalentes em Elixir e Ruby. A thread inteira foi desenhada pra provocar, e provocou.&lt;/p&gt;
&lt;blockquote class="twitter-tweet" data-partner="tweetdeck"&gt;&lt;p lang="en" dir="ltr"&gt;Last conversion of the Campfire from my token stash goes to JavaScript/Express. As with the others, I'm sure you can find more to optimize. But this gives you a sense of what order of magnitude these options exist within with agent-led development. https://t.co/UUapL99SJE https://t.co/2H2NXfeZog&lt;/p&gt;</description><content:encoded><![CDATA[<p>Semana passada o DHH resolveu farmar engajamento com o formato mais preguiçoso que existe: converteu o Campfire, o app de chat dele, pra várias linguagens usando agentes de IA, e foi soltando os resultados de throughput um a um no X. Na série que ele postou, a versão em Rust saiu na frente das equivalentes em Elixir e Ruby. A thread inteira foi desenhada pra provocar, e provocou.</p>
<blockquote class="twitter-tweet" data-partner="tweetdeck"><p lang="en" dir="ltr">Last conversion of the Campfire from my token stash goes to JavaScript/Express. As with the others, I'm sure you can find more to optimize. But this gives you a sense of what order of magnitude these options exist within with agent-led development. https://t.co/UUapL99SJE https://t.co/2H2NXfeZog</p>&mdash; DHH (@dhh) <a href="https://twitter.com/dhh/status/2107077808182976817">October 5, 2026</a></blockquote>
<p>Não vou defender ele dessa vez. Uma conversão única, feita por agente, sem ajuste fino nenhum, não mede linguagem: mede o quanto o modelo conhece os idiomas de cada stack. É rage bait e ele sabe. Dito isso, eu faço o mesmo de vez em quando, e metade dos críticos dele também, então somos todos hipócritas e segue o jogo.</p>
<p>E o ponto fraco da metodologia dele virou tópico por conta própria: várias pessoas notaram que a versão em Rust tinha passado por bem mais iterações de agente que as outras, ou seja, mais rodadas de otimização. O @kurtome respondeu na prática: pegou a versão em Elixir e subiu as requisições da página principal em 10x, com otimizações óbvias que nenhuma primeira conversão faz, tipo cache e pooling assíncrono de conexões de banco.</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">@dhh Here you go https://t.co/gUtK7Z7zG7<br><br>got a 10x speed up on the Room page requests, and sped up all the other requests significantly as well. the biggest gains came from some no brainers: implement caching and pool the db connections with proper async https://t.co/mbp94q9N0s</p>&mdash; kurt (.com) (@kurtome) <a href="https://twitter.com/kurtome/status/2106902762076467681">October 5, 2026</a></blockquote>
<p>A comparação dele, então, não era &ldquo;Rust contra Elixir&rdquo;: era &ldquo;Rust depois de N rodadas de ajuste contra Elixir depois de uma&rdquo;. E isso escancara a verdade que a thread finge ignorar: <strong>nenhum software de verdade sai com meia dúzia de prompts</strong>. Codar com IA brilha quando um engenheiro experiente pilota o agente rumo ao objetivo dele, e isso significa dezenas de iterações, não três.</p>
<p>A parte boa é que a isca foi útil pra mim. Eu venho fazendo <a href="/2026/09/15/novo-llm-benchmark-v4-retestando-todos-llms-parte-1/">benchmarks de LLM</a> e <a href="/2026/10/10/qual-modelo-de-decisao-classificador-de-ia-tipo-jev-e-melhor/">benchmark de modelos de decisão</a>, mas nunca tinha medido, de forma reproduzível, a pergunta que essa thread fingia responder: <strong>faz diferença qual linguagem você escolhe quando quem escreve o código é um LLM?</strong> Minha resposta curta já estava dada em dois posts meus:</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Pressure under load is not a trivial problem to solve from scratch and I recommend you do not attempt to. It's a solved problem already and all solutions converge to what Elixir already brings out of the box. So yes. I use Rust all the time, but if I had to solve high scalability problems (particularly high volume message queues), I would think of Elixir first.</p>&mdash; Fabio Akita (@AkitaOnRails) <a href="https://twitter.com/AkitaOnRails/status/2108587462595367353">October 9, 2026</a></blockquote>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Language wars are tiresome. And the reality is simpler if you're doing AI assisted coding.<br><br>- system level (drivers, fs, daemons, etc): Rust. C++ is you're extending existing legacy<br>- windows only desktop apps: C#<br>- iOS/MacOS/iPadOS: Swift<br>- multi-OS: Rust/Tauri, Qt/C++, Flutter, Electron (pick your poison)<br>- simple websites: Node/Typescript/Bun/Deno<br>- highly scalable backend: Elixir, parts in Rust, parts in Go<br>- CLI tools: Rust<br><br>Everything else, by default, Rust. Unless it's an edge case.</p>&mdash; Fabio Akita (@AkitaOnRails) <a href="https://twitter.com/AkitaOnRails/status/2108590592263352540">October 9, 2026</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
<h2>Não existe linguagem melhor, existe ferramenta certa<span class="hx:absolute hx:-mt-20" id="não-existe-linguagem-melhor-existe-ferramenta-certa"></span>
    <a href="#n%c3%a3o-existe-linguagem-melhor-existe-ferramenta-certa" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Aquela lista do post não é ranking, é mapa de terreno. Cada linguagem e cada framework foram desenhados pra um caso de uso, e nenhum cobre todos. Isso valia quando você escrevia cada linha na mão e continua valendo com agente. O LLM não muda a física do problema: ele só muda quem digita.</p>
<p>E o mapa vai além da linguagem. Sistema real tem infraestrutura em volta: Kubernetes quando a escala pede, SQLite por padrão porque a maioria dos apps nunca precisa de mais que isso, Postgres com pgBouncer quando precisa, Redis pra cache e chave-valor, RabbitMQ ou NATS pra fila. Escolher &ldquo;a stack&rdquo; no dia zero e achar que ela dura dez anos é fantasia. Não dura, e tudo bem.</p>
<p>Exemplo pessoal: web app novo, eu abro Ruby on Rails sem pensar duas vezes. É a solução mais completa de ponta a ponta que existe pra web, e com LLM escrevendo a maior parte do código, a produtividade do framework pesa ainda mais que a da linguagem. Conforme o sistema cresce, eu vou trocando as partes que pedem outra ferramenta: fila de alto volume sai do lado do Rails e vai pra algo da terra Erlang, começando por RabbitMQ, que já resolve a parte difícil de backpressure pra mim.</p>
<blockquote>
  <p>Nenhum software bom está pronto: ele cresce. O uso dita o que você substitui, reescreve e adiciona. A arquitetura final nunca é a do primeiro commit, e quem tenta acertar tudo no primeiro commit só gera documento morto.</p>

</blockquote>
<h2>A superstição dos tokens<span class="hx:absolute hx:-mt-20" id="a-superstição-dos-tokens"></span>
    <a href="#a-supersti%c3%a7%c3%a3o-dos-tokens" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A thread do DHH ressuscitou duas crenças que circulam desde que LLM virou ferramenta de trabalho:</p>
<ul>
<li><strong>&ldquo;Linguagem de script é melhor pra IA porque a sintaxe enxuta gasta menos tokens.&rdquo;</strong></li>
<li><strong>&ldquo;Linguagem fortemente tipada é melhor pra IA porque o compilador pega os erros, então precisa de menos teste e menos reparo.&rdquo;</strong></li>
</ul>
<p>As duas soam razoáveis e as duas são superstição até alguém medir. Eu apostava contra as duas, por experiência: compilador bom ajuda, mas não faz mágica, e concisão de sintaxe nunca foi o gargalo de um agente que lê e escreve arquivo o dia inteiro.</p>
<p>O que realmente separa projeto que anda de projeto que gira em círculo é disciplina de teste. Já escrevi que <a href="/2026/04/20/clean-code-para-agentes-de-ia/">TDD virou obrigação técnica, não filosofia</a>, e o benchmark deste texto é mais um dado na mesma direção. Não importa se você faz test-first ou test-after: importa que a suíte cresça junto com o código, que <a href="/2026/03/01/software-nunca-esta-pronto-4-projetos-a-vida-pos-deploy-e-por-que-one-shot-prompt-e-mito/">todo bug reportado por usuário vire teste de regressão</a> antes da correção, e que você nunca repita o mesmo erro duas vezes. Software sem usuário é software morto, então <a href="/2026/09/22/parem-de-inventar-desculpas-e-facam-mais-deploy-a-premissa-mudou/">release cedo, release sempre</a>, quebra rápido e conserta mais rápido ainda. IA fecha exatamente essa lacuna: o ciclo de reproduzir, testar e corrigir ficou barato. QA virou a habilidade mais valiosa do programador, muito acima de &ldquo;saber a sintaxe de Rust&rdquo;.</p>
<p>Disciplina vence compilador e vence linguagem. Essa era a minha hipótese. Foi medir.</p>
<h2>O benchmark<span class="hx:absolute hx:-mt-20" id="o-benchmark"></span>
    <a href="#o-benchmark" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Montei um experimento reproduzível, público no <a href="https://github.com/akitaonrails/vibe-coding-benchmarks"target="_blank" rel="noopener">repositório vibe-coding-benchmarks</a>, com os resultados por tentativa publicados em JSON e os rastros brutos fora do repo por contenção de credenciais. Não é um campeonato de linguagem, e o próprio design repete isso: é uma fotografia de uma tarefa real, medindo o que dá pra medir com honestidade.</p>
<h3>A tarefa<span class="hx:absolute hx:-mt-20" id="a-tarefa"></span>
    <a href="#a-tarefa" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Uma tarefa só, de propósito: manter um <strong>motor de livro-razão</strong> (ledger) multilocatário. O agente recebe um código v1 funcionando e precisa levá-lo a um contrato v2, com identidades por tenant, autorização, snapshots serializáveis, índice secundário único com troca de alias, journal com limite de capacidade, recuperação de grupos rasgados, checkpoint, migração de schema legado e IDs de retry duráveis. Não é FizzBuzz: é o tipo de sistema que exige invariante atravessando operações, exatamente onde LLM costuma tropeçar.</p>
<p>A correção é julgada por <strong>43 histórias de teste aplicadas depois da coleta</strong>, fora da vista do agente: as 27 públicas do T1 mais 16 ocultas. Passar é 43 de 43. As condições:</p>
<ul>
<li><strong>T0:</strong> contrato em prosa, exemplos de entrada e saída, um launcher funcionando. Sem suíte de testes.</li>
<li><strong>T1:</strong> o mesmo, mais 27 histórias públicas e um driver pra rodá-las.</li>
</ul>
<p>Cada tentativa tinha 15 minutos de orçamento, com no máximo duas continuações de 3 minutos. Na prática, nenhuma das 112 precisou de continuação.</p>
<h3>As linguagens e os modelos<span class="hx:absolute hx:-mt-20" id="as-linguagens-e-os-modelos"></span>
    <a href="#as-linguagens-e-os-modelos" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Sete linguagens em três estratos, com os toolchains pinados e o Rust em modo seguro:</p>
<ul>
<li><strong>Nativas tipadas:</strong> Rust (safe, pinado), Haskell (GHC pinado).</li>
<li><strong>VM tipadas:</strong> C# (com nullable analysis), Kotlin, TypeScript (<code>strict</code>, <code>noUncheckedIndexedAccess</code>, <code>exactOptionalPropertyTypes</code>).</li>
<li><strong>Dinâmicas de script:</strong> Python e Ruby, sem checker nenhum.</li>
</ul>
<p>Quatro configurações de modelo, todas por assinatura, sem fallback pago: <strong>Kimi K3</strong> (high), <strong>GLM 5.3</strong> via OpenCode (high), <strong>Claude Opus 5.5</strong> (medium) e <strong>Codex com gpt-6 Sol</strong> (medium). Cruzando tudo: 7 linguagens × 4 modelos × 2 condições × 2 repetições = <strong>112 tentativas</strong>.</p>
<h3>O que esse design não mede<span class="hx:absolute hx:-mt-20" id="o-que-esse-design-não-mede"></span>
    <a href="#o-que-esse-design-n%c3%a3o-mede" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Antes dos números, os freios, porque eles fazem parte do resultado. É uma tarefa só, com duas repetições por célula: amostra pequena por construção. Cobertura de tokens é parcial em alguns modelos (dado faltante não é zero). O harness mudou no meio da campanha em três épocas de instrumentação. E atrasos de provedor contaminam tempo de parede: uma das tentativas do Opus passou metade do relógio esperando liberação de cota. Todos os números abaixo são descritivos, e eu aponto onde eles não sustentam conclusão.</p>
<p>Tem um freio adicional, de propósito: <strong>o nível de iteração</strong>. Meu desenho mede o que dá pra alcançar em todas as linguagens com níveis parecidos de iteração, em orçamento curto, sem um humano ajustando depois. É fotografia do piso, não do teto. Qualquer uma das 112 implementações provavelmente melhora com mais rodadas de agente, e as quatro stacks de fila idem: o que eu publico é o que cada linguagem entrega &ldquo;de fábrica&rdquo; sob o mesmo esforço, não o máximo que um engenheiro paciente extrairia dela.</p>
<blockquote>
  <p><strong>Disclaimer obrigatório antes de citar qualquer número deste texto por aí:</strong> todos os resultados a seguir valem pra <strong>este</strong> benchmark, com <strong>esta</strong> tarefa, <strong>estes</strong> modelos, <strong>estes</strong> prompts e <strong>este</strong> hardware. O seu caso pode variar violentamente. Se a sua tarefa não se parece com um motor de livro-razão, se o seu modelo não é um destes quatro, se a sua fila não é a minha fila, os números não se transferem. O código, as tarefas, as notas e o passo a passo pra reproduzir estão todos no <a href="https://github.com/akitaonrails/vibe-coding-benchmarks"target="_blank" rel="noopener">repositório no GitHub</a>. E se você discordar de algum resultado e essa discordância for importante pra sua empresa ou pro seu projeto, a única atitude honesta é a que eu tive: <strong>pare de argumentar e rode o seu próprio benchmark.</strong></p>

</blockquote>
<h2>Resultado 1: em acerto, os modelos de fronteira empataram no teto<span class="hx:absolute hx:-mt-20" id="resultado-1-em-acerto-os-modelos-de-fronteira-empataram-no-teto"></span>
    <a href="#resultado-1-em-acerto-os-modelos-de-fronteira-empataram-no-teto" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><table>
  <thead>
      <tr>
          <th>Configuração</th>
          <th style="text-align: right">Corretas no prazo</th>
          <th style="text-align: right">Corretas no limite</th>
          <th style="text-align: right">Falhas</th>
          <th style="text-align: right">Sem nota</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Kimi K3 high</td>
          <td style="text-align: right">17</td>
          <td style="text-align: right">7</td>
          <td style="text-align: right">2</td>
          <td style="text-align: right">2</td>
      </tr>
      <tr>
          <td>GLM 5.3 high (OpenCode)</td>
          <td style="text-align: right">7</td>
          <td style="text-align: right">9</td>
          <td style="text-align: right">7</td>
          <td style="text-align: right">5</td>
      </tr>
      <tr>
          <td>Opus 5.5 medium</td>
          <td style="text-align: right">25</td>
          <td style="text-align: right">0</td>
          <td style="text-align: right">3 (interrupção de infra)</td>
          <td style="text-align: right">0</td>
      </tr>
      <tr>
          <td>Codex / Sol medium</td>
          <td style="text-align: right">28</td>
          <td style="text-align: right">0</td>
          <td style="text-align: right">0</td>
          <td style="text-align: right">0</td>
      </tr>
  </tbody>
</table>
<p>Leitura: Sol passou nas 28 tentativas elegíveis e o Opus nas 25 dele (as 3 falhas foram infraestrutura, não código). <strong>53 de 53 tentativas elegíveis corretas entre os dois.</strong> A tarefa, feita pra ser difícil, foi fácil demais pra separar os modelos de fronteira, e muito menos as linguagens: com todo mundo passando em tudo, não existe ranking de acerto pra publicar.</p>
<p>A diferença apareceu nos modelos mais fracos, e na direção que a minha hipótese previa: <strong>teste fornecido ajudou</strong>. Kimi e GLM, com a suíte pública em mãos (T1), entregaram 24 artefatos corretos contra 16 sem ela (T0). O Kimi, em particular, passou nas 14 tentativas com testes fornecidos. É descritivo, não causal, mas é o sinal mais limpo do benchmark inteiro: o que mudou o resultado não foi a linguagem, foi a presença de teste.</p>
<p>Por linguagem, dentro de cada modelo, o acerto ficou espalhado entre 1/4 e 4/4 sem padrão que se repita entre modelos. As três falhas semânticas que eu auditei a fundo são instrutivas: um C# do GLM e um Python do Kimi perderam a mesma história por violar a mesma invariante entre operações, e um Haskell do Kimi errou um caminho de checkpoint. Nenhuma falha era erro de tipo.</p>
<blockquote>
  <p>Onde o modelo errou, errou invariante de domínio: o tipo de erro que compilador nenhum pega, em linguagem tipada ou dinâmica. O mito de que tipagem forte poupa o trabalho de entender o problema morre aí.</p>

</blockquote>
<h2>Resultado 2: linguagem concisa não economizou token<span class="hx:absolute hx:-mt-20" id="resultado-2-linguagem-concisa-não-economizou-token"></span>
    <a href="#resultado-2-linguagem-concisa-n%c3%a3o-economizou-token" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Aqui vai a tabela que mata a primeira superstição. Medianas por linguagem no Sol, quatro tentativas válidas por célula:</p>
<table>
  <thead>
      <tr>
          <th>Linguagem</th>
          <th style="text-align: right">Tempo mediano</th>
          <th style="text-align: right">Tokens medianos</th>
          <th style="text-align: right">Linhas finais do código</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>TypeScript</td>
          <td style="text-align: right">53 s</td>
          <td style="text-align: right">147,5 mil</td>
          <td style="text-align: right">~280</td>
      </tr>
      <tr>
          <td>Python</td>
          <td style="text-align: right">77 s</td>
          <td style="text-align: right">138,2 mil</td>
          <td style="text-align: right">~300</td>
      </tr>
      <tr>
          <td>C#</td>
          <td style="text-align: right">61 s</td>
          <td style="text-align: right">171,6 mil</td>
          <td style="text-align: right">~260</td>
      </tr>
      <tr>
          <td>Kotlin</td>
          <td style="text-align: right">65 s</td>
          <td style="text-align: right">170,6 mil</td>
          <td style="text-align: right">~210</td>
      </tr>
      <tr>
          <td>Haskell</td>
          <td style="text-align: right">75 s</td>
          <td style="text-align: right">184,7 mil</td>
          <td style="text-align: right">~265</td>
      </tr>
      <tr>
          <td>Ruby</td>
          <td style="text-align: right">82 s</td>
          <td style="text-align: right">185,6 mil</td>
          <td style="text-align: right">~380</td>
      </tr>
      <tr>
          <td>Rust</td>
          <td style="text-align: right">80 s</td>
          <td style="text-align: right">188,1 mil</td>
          <td style="text-align: right">~650</td>
      </tr>
  </tbody>
</table>
<p>Ruby, o mais conciso em linhas (pouco mais da metade do Rust), custou praticamente os mesmos tokens do Rust no Sol, e no Opus foi <strong>o mais caro de todos</strong>, uns 205 mil tokens de mediana contra 94 mil do C#. Os mais baratos foram TypeScript e Python no Sol, C#, Haskell e Kotlin no Opus. Se &ldquo;menos linhas de código fonte = menos tokens de LLM&rdquo; fosse lei, Ruby tinha que liderar de longe. Ficou no fim.</p>
<p>E a segunda superstição? Auditei os rastros procurando os ciclos de reparo guiados por compilador que a tipagem forte supostamente evita. Resultado: <strong>zero ciclos de reparo de erro de compilação</strong> nas tentativas auditadas do Opus, em Ruby, Rust e Haskell. O Rust não precisou de correção nenhuma vinda do compilador; o Haskell teve uma limpeza de warning; o Ruby gastou seus tokens extras em ajuste voluntário de performance. O modelo de fronteira quase sempre escreve código que compila de primeira, e o esforço real vai pra verificação, não pra apaziguar o compilador.</p>
<p>Um contraponto que fecha o caixão da tese &ldquo;barato é bom&rdquo;: as células de <strong>menor</strong> consumo de token do GLM foram as falhas. TypeScript T0 por 50 mil tokens parece eficiente até você ver que são dois starters inalterados porque o modelo estourou o limite de saída. Token baixo pode significar desistência precoce.</p>
<h2>Resultado 3: sob carga, todo mundo empatou na fila<span class="hx:absolute hx:-mt-20" id="resultado-3-sob-carga-todo-mundo-empatou-na-fila"></span>
    <a href="#resultado-3-sob-carga-todo-mundo-empatou-na-fila" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Aí vem a pergunta do DHH: Rust &ldquo;ganha&rdquo; de Elixir e Ruby quando o assunto é throughput. Montei a trilha separada pra isso: quatro workers de fila contra um RabbitMQ real, escritos com ajuda de agente, cada um com seu idioma natural:</p>
<ul>
<li><strong>Elixir</strong> com OTP e Broadway</li>
<li><strong>Rust</strong> com Tokio e Lapin</li>
<li><strong>Rails</strong> com Bunny</li>
<li><strong>Ruby puro</strong> com Bunny (controle)</li>
</ul>
<p>Um gerador publicava 10 mil mensagens de 4 KB no RabbitMQ, a 200 por segundo, e cada worker consumia o mais rápido que conseguia, batendo os efeitos num coletor HTTP/SQLite com atraso nominal de 50 ms, limitado a 2 CPUs e 1 GB de RAM. Dois perfis de concorrência:</p>
<table>
  <thead>
      <tr>
          <th>Stack</th>
          <th style="text-align: right">Handlers</th>
          <th style="text-align: right">Efeitos/s</th>
          <th style="text-align: right">Corretas/10.000</th>
          <th style="text-align: right">Pico de memória</th>
          <th>Drenou?</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Elixir</td>
          <td style="text-align: right">8</td>
          <td style="text-align: right">94,8</td>
          <td style="text-align: right">10.000</td>
          <td style="text-align: right">135 MiB</td>
          <td>sim</td>
      </tr>
      <tr>
          <td>Rust</td>
          <td style="text-align: right">8</td>
          <td style="text-align: right">94,6</td>
          <td style="text-align: right">10.000</td>
          <td style="text-align: right">16 MiB</td>
          <td>sim</td>
      </tr>
      <tr>
          <td>Rails</td>
          <td style="text-align: right">8</td>
          <td style="text-align: right">92,7</td>
          <td style="text-align: right">10.000</td>
          <td style="text-align: right">79 MiB</td>
          <td>sim</td>
      </tr>
      <tr>
          <td>Ruby</td>
          <td style="text-align: right">8</td>
          <td style="text-align: right">92,5</td>
          <td style="text-align: right">10.000</td>
          <td style="text-align: right">38 MiB</td>
          <td>sim</td>
      </tr>
      <tr>
          <td>Elixir</td>
          <td style="text-align: right">64</td>
          <td style="text-align: right">84,8</td>
          <td style="text-align: right">7.915</td>
          <td style="text-align: right">161 MiB</td>
          <td>não</td>
      </tr>
      <tr>
          <td>Rust</td>
          <td style="text-align: right">64</td>
          <td style="text-align: right">88,2</td>
          <td style="text-align: right">8.151</td>
          <td style="text-align: right">23 MiB</td>
          <td>não</td>
      </tr>
      <tr>
          <td>Rails</td>
          <td style="text-align: right">64</td>
          <td style="text-align: right">88,5</td>
          <td style="text-align: right">8.108</td>
          <td style="text-align: right">87 MiB</td>
          <td>não</td>
      </tr>
      <tr>
          <td>Ruby</td>
          <td style="text-align: right">64</td>
          <td style="text-align: right">88,0</td>
          <td style="text-align: right">8.081</td>
          <td style="text-align: right">60 MiB</td>
          <td>não</td>
      </tr>
  </tbody>
</table>
<p>No perfil de 8 handlers, as quatro stacks entregaram a mesma vazão, 93 a 95 efeitos por segundo, e drenaram a fila inteira. O gargalo aparente era o coletor compartilhado (um lock em Python, um escritor SQLite), não as linguagens. No perfil de 64 handlers, todas deixaram backlog e estouraram o timeout do cliente em massa, então marquei essas células como não qualificadas: o protocolo forte, com coletor de group-commit e escada de 50 a 800 msg/s, está desenhado e não executado.</p>
<p>A única diferença que sobrou de pé nos números foi <strong>memória</strong>: Rust segurou o pico em 16 MiB onde a BEAM gastou 135. É uma vantagem real do Rust em densidade, com a ressalva de que pico de cgroup não é footprint de linguagem. Em vazão, que era a alegação da thread, empate técnico.</p>
<p>E o empate merece a leitura honesta: ele pode significar só que a minha pressão foi pouca. Com o coletor compartilhado segurando tudo em torno de 95 efeitos por segundo, qualquer stack capaz de mais que isso aparece como &ldquo;igual&rdquo;. O que esse teste mostra é onde estava o gargalo, não onde cada stack quebra. Pra achar o teto de cada uma eu precisaria do protocolo forte, com coletor de group-commit e escada de carga, que está desenhado e não executado. Até lá, &ldquo;todas as stacks performam igual&rdquo; é a conclusão errada; &ldquo;todas ficaram acima do que o meu teste conseguiu medir&rdquo; é a certa.</p>
<p>E a pergunta que interessava mais, &ldquo;o agente trabalha menos pra entregar um consumer de fila confiável em Elixir do que em Rust?&rdquo;, ficou sem resposta de propósito: essas quatro implementações foram escritas pra validar o ambiente, não como tentativa controlada de codificação. Comparar o esforço de IA entre Elixir e Rust exigiria a mesma matriz do ledger rodando na tarefa de fila. Está na lista.</p>
<h2>Afinal, a linguagem importa?<span class="hx:absolute hx:-mt-20" id="afinal-a-linguagem-importa"></span>
    <a href="#afinal-a-linguagem-importa" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Com os dados na mesa, dá pra responder as três perguntas da thread:</p>
<p><strong>Existe linguagem melhor pra programar com LLM?</strong> Não apareceu nenhuma. Nos modelos de fronteira, acerto e esforço variaram menos entre linguagens do que entre modelos. Onde houve diferença de acerto, ela seguiu a presença de testes fornecidos, não a sintaxe. &ldquo;Nenhum ranking geral de linguagem se estabelece&rdquo; é a frase oficial do relatório, e é a frase correta.</p>
<p><strong>As teses de economia de token se sustentam?</strong> Nenhuma das duas. Concisão não economizou: Ruby custou tanto quanto Rust, ou mais. Tipagem forte não poupou reparo: não havia reparo de tipo pra poupar. O esforço dos modelos foi pra verificação e entendimento do contrato, e isso não tem linguagem.</p>
<p><strong>O DHH estava certo?</strong> No que ele mediu, ele mediu o que um agente entrega de graça em cada stack, sem ajuste, e chamou isso de veredito sobre linguagem. No meu teste, com carga real e medição honesta, as quatro stacks empataram em vazão no regime estável, e só a memória diferenciou o Rust. Se a thread dele prova alguma coisa, prova que conversão por agente mede o modelo, não a linguagem.</p>
<p>Minhas apostas da introdução saíram de pé: teste e disciplina são a alavanca, o compilador é coadjuvante, e a ferramenta certa depende do problema. Web app novo continua sendo Rails na minha mão, fila pesada continua sendo território Erlang, sistema baixo nível continua sendo Rust, e o LLM escreve qualquer uma delas com a mesma competência. Quem passou a semana brigando pela linguagem favorita no X brigou por nada.</p>
<p>E a minha prática diária conta a mesma história. Eu tenho viés pessoal pra Rust e mesmo assim já publiquei mais de 30 projetos open source em todo tipo de stack: o <a href="https://github.com/akitaonrails/ai-memory"target="_blank" rel="noopener">ai-memory</a> é Rust, o <a href="https://github.com/akitaonrails/FrankMD"target="_blank" rel="noopener">FrankMD</a> é Ruby on Rails, o <a href="https://github.com/akitaonrails/ai-memory-web"target="_blank" rel="noopener">ai-memory-web</a> e o <a href="https://github.com/akitaonrails/ai-jail-web"target="_blank" rel="noopener">ai-jail-web</a> são Node.js. Em nenhum deles eu senti diferença relevante que viesse da stack: os ganhos foram parecidos, os erros também, e os erros eram sempre compreensíveis. Cada erro virou melhoria de invariante, de regra, de skill. Nunca virou troca de linguagem.</p>
<p>Então esquece &ldquo;economia de token&rdquo; e &ldquo;superstição de tipagem&rdquo;. Faz engenharia de software de verdade: escolhe a ferramenta certa pro teu problema específico, aplica técnica de engenharia correta, e qualquer agente razoável vai performar mais ou menos igual. E para de gastar tempo discutindo rage bait de guerra de linguagem na internet. Seja engenheiro de software de verdade e faça engenharia de verdade. É só isso.</p>
<p>Os dados por tentativa, a metodologia completa e os limites de evidência estão no <a href="https://github.com/akitaonrails/vibe-coding-benchmarks"target="_blank" rel="noopener">repositório</a>. Se você rodar a matriz com outra tarefa e achar um ranking que eu não achei, me mostra.</p>
]]></content:encoded><category>benchmarks-de-llm</category><category>linguagens-de-programacao</category><category>llms</category><category>elixir-e-erlang</category></item><item><title>Ex Manga Downloadr - Parte 7: Lidando Corretamente com Grandes Coleções</title><link>https://akitaonrails.github.io/2017/06/16/ex-manga-downloadr-parte-7-lidando-com-grandes-colecoes/</link><guid isPermaLink="true">https://akitaonrails.github.io/2017/06/16/ex-manga-downloadr-parte-7-lidando-com-grandes-colecoes/</guid><pubDate>Fri, 16 Jun 2017 15:10:00 -0300</pubDate><description>&lt;p&gt;No &lt;a href="http://www.akitaonrails.com/2017/06/13/ex-manga-downloadr-parte-6-a-ascensao-do-flow"target="_blank" rel="noopener"&gt;post anterior&lt;/a&gt; consegui simplificar bastante o código original usando &lt;a href="https://github.com/elixir-lang/flow"target="_blank" rel="noopener"&gt;Flow&lt;/a&gt;. Só que o lado negativo foi que o tempo de execução aumentou consideravelmente.&lt;/p&gt;
&lt;p&gt;José Valim gentilmente apareceu e deixou um &lt;a href="http://www.akitaonrails.com/2017/06/13/ex-manga-downloadr-parte-6-a-ascensao-do-flow#comment-3360301947"target="_blank" rel="noopener"&gt;comentário valioso&lt;/a&gt;, que vou reproduzir aqui:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Você tentou reduzir o &lt;code&gt;@max_demand&lt;/code&gt;? O &lt;code&gt;@max_demand&lt;/code&gt; é o quanto de dados você troca entre os estágios. Se você colocar 60, isso significa que está mandando 60 itens para um estágio, 60 para outro e assim por diante. Isso gera um balanceamento ruim para coleções pequenas, porque há chance de todos os itens acabarem no mesmo estágio. Na verdade você quer reduzir o &lt;code&gt;max_demand&lt;/code&gt; para 1 ou 2, para que cada estágio receba lotes pequenos e precise pedir mais. Outro parâmetro que costuma ser ajustado é a opção &lt;code&gt;stages: ...&lt;/code&gt; — você deveria definir como a quantidade de conexões que tinha no poolboy antes.&lt;/p&gt;</description><content:encoded><![CDATA[<p>No <a href="http://www.akitaonrails.com/2017/06/13/ex-manga-downloadr-parte-6-a-ascensao-do-flow"target="_blank" rel="noopener">post anterior</a> consegui simplificar bastante o código original usando <a href="https://github.com/elixir-lang/flow"target="_blank" rel="noopener">Flow</a>. Só que o lado negativo foi que o tempo de execução aumentou consideravelmente.</p>
<p>José Valim gentilmente apareceu e deixou um <a href="http://www.akitaonrails.com/2017/06/13/ex-manga-downloadr-parte-6-a-ascensao-do-flow#comment-3360301947"target="_blank" rel="noopener">comentário valioso</a>, que vou reproduzir aqui:</p>
<blockquote>
  <p>Você tentou reduzir o <code>@max_demand</code>? O <code>@max_demand</code> é o quanto de dados você troca entre os estágios. Se você colocar 60, isso significa que está mandando 60 itens para um estágio, 60 para outro e assim por diante. Isso gera um balanceamento ruim para coleções pequenas, porque há chance de todos os itens acabarem no mesmo estágio. Na verdade você quer reduzir o <code>max_demand</code> para 1 ou 2, para que cada estágio receba lotes pequenos e precise pedir mais. Outro parâmetro que costuma ser ajustado é a opção <code>stages: ...</code> — você deveria definir como a quantidade de conexões que tinha no poolboy antes.</p>

</blockquote>
<blockquote>
  <p>No entanto, acredito que você não precisa usar Flow de jeito nenhum. O Elixir v1.4 tem algo chamado <a href="https://hexdocs.pm/elixir/Task.html#async_stream/5"target="_blank" rel="noopener"><code>Task.async_stream</code></a>, que é uma mistura de poolboy + task async, e definitivamente é um substituto melhor para o que você tinha. Adicionamos isso ao Elixir depois de implementar o Flow, quando percebemos que dá pra extrair muito valor do <code>Task.async_stream</code> sem precisar partir para uma solução completa como o Flow. Usando <code>Task.async_stream</code>, a opção <code>max_concurrency</code> controla o tamanho do pool.</p>

</blockquote>
<p>E, obviamente, ele está certo. Eu tinha entendido errado como o Flow funciona. Ele foi feito para quando você tem uma quantidade absurda de itens para processar em paralelo — especialmente unidades de processamento que podem ter alta variância e, por isso, muita back-pressure, não só pela quantidade de itens, mas porque os tempos de execução podem variar dramaticamente. Então é um daqueles casos de ter um canhão na mão quando só tem uma mosca para matar.</p>
<p>O que eu não sabia era da existência do <code>Task.async_stream</code> e seu companheiro <a href="https://hexdocs.pm/elixir/Task.Supervisor.html#async_stream/4"target="_blank" rel="noopener"><code>Task.Supervisor.async_stream</code></a> para quando preciso de mais controle.</p>
<p>Vamos dar um passo atrás.</p>
<h3>Lidando com coleções em Elixir<span class="hx:absolute hx:-mt-20" id="lidando-com-coleções-em-elixir"></span>
    <a href="#lidando-com-cole%c3%a7%c3%b5es-em-elixir" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Erlang é uma fera. Ele fornece todos os blocos de construção de um sistema operacional de tempo real e altamente concorrente! Sério, o que ele entrega out-of-the-box é muito mais do que qualquer outra linguagem/plataforma/máquina virtual, em qualquer época. Em Java, .NET ou qualquer outra coisa, você não tem isso de graça. Precisa montar as peças na mão, gastar centenas de horas ajustando e ainda torcer muito para que tudo funcione de forma integrada.</p>
<p>Então, tem sistemas distribuídos para construir? Não tem outra opção de verdade. Faça Erlang, ou sofra.</p>
<p>Aí o Elixir eleva isso um nível acima, criando uma biblioteca padrão bastante razoável e simples de usar, que torna a parte de escrever código genuinamente agradável. É uma combinação matadora. Precisa fazer o próximo WhatsApp? O próximo Waze? Reconstruir o Cassandra do zero? Criar algo como o Apache Spark? Faça Elixir.</p>
<p>Em Erlang, você resolve tudo usando GenServer. É uma abstração elegante do OTP. Você <a href="http://www.akitaonrails.com/en/2015/11/22/observando-processos-em-elixir-the-little-elixir-otp-guidebook/"target="_blank" rel="noopener">precisa entender OTP</a> de forma íntima. Não tem atalho. Não existe Erlang sem OTP.</p>
<p>Dito isso, dá para começar simples e escalar sem tanto esforço.</p>
<p>Geralmente tudo começa com Coleções, ou mais corretamente, com algum tipo de <a href="https://hexdocs.pm/elixir/Enum.html#content"target="_blank" rel="noopener">Enumeração</a>.</p>
<p>Assim como minha função simples <code>Workflow.pages/1</code>, que itera por uma lista de links de capítulos, busca cada link, faz o parse do HTML retornado e extrai a coleção de links de páginas dentro daquele capítulo, reduzindo as sublistas em uma lista completa de links de páginas.</p>
<p>Se eu sei que a coleção é pequena (menos de 100 itens, por exemplo), simplesmente faria 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">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>Simples assim. É linear. Vai processar um link por vez sequencialmente. Quanto mais links de capítulos, mais tempo demora. Normalmente eu quero processar isso em paralelo. Mas não posso disparar um processo paralelo para cada link de capítulo, porque se receber 1.000 links e disparar todos de uma vez, vira um ataque de negação de serviço e certamente recebo centenas de timeouts.</p>
<p>Dá para esbarrar em 2 problemas principais quando precisa iterar por uma coleção grande.</p>
<ul>
<li>
<p>Se a coleção for enorme (imagine um arquivo de texto de um gigabyte que você precisa iterar linha por linha). Para isso usa-se <a href="https://hexdocs.pm/elixir/Stream.html#content"target="_blank" rel="noopener"><code>Stream</code></a> em vez de <code>Enum</code>. Todas as funções parecem quase idênticas, mas você não precisará carregar a coleção inteira na memória nem ficará duplicando-a.</p>
</li>
<li>
<p>Se a unidade de processamento demora muito. Agora que você resolveu não explodir o uso de memória, e se você tiver jobs lentos enquanto itera por cada item da coleção? É exatamente o nosso caso, onde a coleção é bem pequena, mas o tempo de processamento é longo porque está buscando de uma fonte externa na internet. Pode levar milissegundos, pode levar alguns segundos.</p>
</li>
</ul>
<p>Uma forma de controlar isso é usando &ldquo;batches&rdquo;, 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="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">chunk</span><span class="p">(</span><span class="mi">60</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="n">pages_download</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="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">concat</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><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">pages_download</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></span><span class="line"><span class="cl">   <span class="n">results</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">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">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 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="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 class="n">results</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 é só para exemplo, não compilei esse trecho para verificar se funciona, mas dá para entender a ideia de dividir a lista grande em chunks e processar cada chunk via <code>Task.async</code> e <code>Task.await</code>.</p>
<p>De novo, para listas pequenas isso está ok (milhares de itens) desde que cada item não demore muito para processar.</p>
<p>O problema é que isso não é lá muito bom. Porque cada chunk precisa terminar antes do próximo começar. É por isso que a solução ideal é manter uma quantidade constante de jobs rodando a qualquer momento. Para isso, precisamos de um Pool, como expliquei na <a href="http://www.akitaonrails.com/en/2015/11/19/ex-manga-downloadr-parte-2-poolboy-ao-resgate/"target="_blank" rel="noopener">Parte 2: Poolboy ao resgate!</a>.</p>
<p>Mas implementar do jeito correto para manter o pool sempre cheio exige um malabarismo chato entre transações do Poolboy e <code>Task.Supervisor.async</code>. Foi por isso que fiquei interessado no novo uso do <code>Flow</code>.</p>
<p>O código até fica limpo, mas como já expliquei antes, esse não é o caso de uso adequado para o Flow. É melhor quando você precisa iterar por dezenas de milhares de itens ou até infinitos (quando você tem um fluxo de entrada de requisições que precisam de processamento paralelo, por exemplo).</p>
<p>Então, finalmente, existe um meio-termo. A solução entre o simples <code>Task.async</code> e o <code>Flow</code> é o <code>Task.async_stream</code>, que funciona como uma implementação de pool, mantendo uma quantidade <code>max_concurrency</code> de jobs rodando em stream. O código final fica muito mais elegante 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">Task</span><span class="o">.</span><span class="n">async_stream</span><span class="p">(</span><span class="no">MangaWrapper</span><span class="p">,</span> <span class="ss">:chapter_page</span><span class="p">,</span> <span class="o">[</span><span class="n">source</span><span class="o">]</span><span class="p">,</span> <span class="ss">max_concurrency</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">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="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="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>E esse é o <a href="https://github.com/akitaonrails/ex_manga_downloadr/commit/517183261e998ab40f6e5bc793b4db9adcf899e3"target="_blank" rel="noopener">commit final</a> com as mudanças mencionadas.</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>A implementação com <code>Task.async_stream</code> é super simples e os tempos finalmente voltaram ao que eram antes.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>84,16s user 20,80s system 138% cpu 1:15,94 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>Muito melhor do que os mais de 3 minutos que estava demorando com Flow. E não é porque o Flow é lento — é porque eu não estava usando corretamente, provavelmente jogando um chunk grande em um único GenStage e criando um gargalo. De novo: só use Flow se tiver itens suficientes para colocar centenas deles em vários GenStages em paralelo. Estamos falando de coleções com dezenas de milhares de itens, não da minha mísera lista de páginas.</p>
<p>Tem um pequeno ajuste, porém. Para buscar todos os links de capítulos e páginas estou usando <code>max_concurrency</code> de 100. O timeout padrão é 5000 (5 segundos). Isso funciona porque o HTML retornado não é tão grande e dá para paralelizar tanto assim numa conexão de alta largura de banda.</p>
<p>Mas no procedimento de download de imagens em <code>Workflow.process_downloads</code> tive que cortar o <code>max_concurrency</code> pela metade e aumentar o <code>timeout</code> para 30 segundos para garantir que não travasse.</p>
<p>Como é uma implementação simples, não há recuperação de falhas nem rotina de retry. Teria que substituir essa implementação por <code>Task.Supervisor.async_stream</code> para retomar algum controle. Minha implementação original era mais trabalhosa, mas eu tinha onde adicionar mecanismos de retry. Então, de novo, é sempre uma troca entre facilidade de uso e controle. Quanto mais controle, pior fica o código.</p>
<p>Esse é um exercício simples, então vou deixar assim por enquanto.</p>
]]></content:encoded><category>elixir-e-erlang</category><category>performance</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>Coherence e ExAdmin - Devise e ActiveAdmin para Phoenix</title><link>https://akitaonrails.github.io/2016/12/06/coherence-e-exadmin-devise-e-activeadmin-para-phoenix/</link><guid isPermaLink="true">https://akitaonrails.github.io/2016/12/06/coherence-e-exadmin-devise-e-activeadmin-para-phoenix/</guid><pubDate>Tue, 06 Dec 2016 18:18:00 -0200</pubDate><description>&lt;p&gt;Este post é voltado para Rubyistas pesquisando a possibilidade de substituir parte do Ruby e Rails com Elixir e Phoenix.&lt;/p&gt;
&lt;p&gt;Indo direto ao ponto: muitos dos meus apps Rails pequenos começam com 2 add-ons bem simples — Devise para autenticação e ActiveAdmin para gerenciamento básico do banco de dados. A partir daí construo o resto.&lt;/p&gt;
&lt;p&gt;Tanto Elixir quanto Phoenix são alvos em constante movimento hoje em dia, o que dificulta que um conjunto estável de bibliotecas se solidifique direito. Mas acho que estamos finalmente passando da curva dos early adopters.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Este post é voltado para Rubyistas pesquisando a possibilidade de substituir parte do Ruby e Rails com Elixir e Phoenix.</p>
<p>Indo direto ao ponto: muitos dos meus apps Rails pequenos começam com 2 add-ons bem simples — Devise para autenticação e ActiveAdmin para gerenciamento básico do banco de dados. A partir daí construo o resto.</p>
<p>Tanto Elixir quanto Phoenix são alvos em constante movimento hoje em dia, o que dificulta que um conjunto estável de bibliotecas se solidifique direito. Mas acho que estamos finalmente passando da curva dos early adopters.</p>
<p>Um ponto de atrito considerável sempre foi a autenticação de usuários. Muitos puristas vão argumentar que você precisa construir a sua do zero ou usar bibliotecas de baixo nível como o <a href="https://github.com/ueberauth/guardian"target="_blank" rel="noopener">Guardian</a>.</p>
<p>Se você está construindo uma aplicação que só expõe endpoints de API, tudo bem. Mas para um app web completo feito para humanos usarem, essa dificilmente é uma boa escolha. Não vou entrar nessa discussão hoje, pois foge do ponto.</p>
<p>Assumo que você já seguiu os tutoriais tanto do <a href="http://elixir-lang.org/getting-started/introduction.html"target="_blank" rel="noopener">Elixir</a> quanto do <a href="http://www.phoenixframework.org/docs/installation"target="_blank" rel="noopener">Phoenix</a>. Se ainda não fez, vá lá e faça — vai levar uns um ou dois dias para aprender o básico se você já é um Rubyista experiente. Depois volta e lê <a href="/elixir">meus posts sobre Elixir</a> para entender onde ele se destaca em relação ao resto.</p>
<p>Dito isso, vamos começar.</p>
<h3>Coherence (alternativa ao Devise)<span class="hx:absolute hx:-mt-20" id="coherence-alternativa-ao-devise"></span>
    <a href="#coherence-alternativa-ao-devise" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Finalmente encontrei este projeto que está em desenvolvimento intenso há 6 meses chamado <a href="https://github.com/smpallen99/coherence"target="_blank" rel="noopener">Coherence</a>. Para todos os efeitos práticos, ele imita o Devise em quase todos os aspectos. E isso é muito bom para uma série de cenários.</p>
<p>O <a href="https://github.com/smpallen99/coherence/blob/master/README.md"target="_blank" rel="noopener">README</a> deles é bem explicado, então não vou copiar e colar aqui — é só ler lá para colocar no ar. Mas se você quiser testar todas as funcionalidades, pode ajustar o procedimento com esse conjunto de opções na task de instalação do Mix:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>mix coherence.install --full --rememberable --invitable --trackable</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>Execute <code>mix help coherence.install</code> para ver a descrição de todas as opções.</p>
<p>E se você não for mexer no front-end, pode simplesmente adicionar os links de sign up, sign in e sign out ao layout adicionando o seguinte trecho em <code>web/templates/layout/app.html.eex</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">header</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;header&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">nav</span> <span class="na">role</span><span class="o">=</span><span class="s">&#34;navigation&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">ul</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;nav nav-pills pull-right&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="err">&lt;</span>%= if Coherence.current_user(@conn) do %&gt;
</span></span><span class="line"><span class="cl">        <span class="err">&lt;</span>%= if @conn.assigns[:remembered] do %&gt;
</span></span><span class="line"><span class="cl">          <span class="p">&lt;</span><span class="nt">li</span> <span class="na">style</span><span class="o">=</span><span class="s">&#34;color: red;&#34;</span><span class="p">&gt;</span>!!<span class="p">&lt;/</span><span class="nt">li</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="err">&lt;</span>% end %&gt;
</span></span><span class="line"><span class="cl">      <span class="err">&lt;</span>% end %&gt;
</span></span><span class="line"><span class="cl">      <span class="err">&lt;</span>%= YourApp.Coherence.ViewHelpers.coherence_links(@conn, :layout) %&gt;
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">li</span><span class="p">&gt;&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;http://www.phoenixframework.org/docs&#34;</span><span class="p">&gt;</span>Get Started<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;&lt;/</span><span class="nt">li</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">ul</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">nav</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">header</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>(A propósito, sempre que você ver <code>YourApp</code> nos trechos de código, troque pelo nome do módulo da sua aplicação.)</p>
<p>Se você se perder na documentação deles, pode consultar o repositório <a href="https://github.com/smpallen99/coherence_demo"target="_blank" rel="noopener">Coherence Demo</a> para ver um exemplo de app Phoenix básico com Coherence já configurado e funcionando. Você vai precisar principalmente cuidar do <code>web/router.ex</code> para criar um pipeline <code>:protected</code> e configurar os escopos adequadamente.</p>
<p>Se fizer corretamente, é isso que você vai ver:</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/572/Screen_Shot_2016-12-06_at_17.45.47.png" alt="Coherence Navigation Links"  loading="lazy" /></p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/573/Screen_Shot_2016-12-06_at_17.45.52.png" alt="Coherence Sign In Form"  loading="lazy" /></p>
<p>Faz tempo que eu não ficava animado com uma simples página de login!</p>
<h3>Ex Admin (alternativa ao ActiveAdmin)<span class="hx:absolute hx:-mt-20" id="ex-admin-alternativa-ao-activeadmin"></span>
    <a href="#ex-admin-alternativa-ao-activeadmin" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O próximo passo que costumo dar é adicionar uma interface de administração simples. Para isso encontrei o <a href="https://github.com/smpallen99/ex_admin"target="_blank" rel="noopener">Ex Admin</a>, que está em desenvolvimento ativo desde pelo menos maio de 2015. É tão parecido com ActiveAdmin que o tema antigo deles vai te fazer esquecer que não está num app Rails.</p>
<p>De novo, é bem direto configurar — basta seguir as instruções do <a href="https://github.com/smpallen99/ex_admin"target="_blank" rel="noopener">README</a> deles.</p>
<p>Com tudo instalado e configurado, você consegue expor o model User na interface Admin rapidamente assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>mix admin.gen.resource User</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 editar o <code>web/admin/user.ex</code> com 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="n">defmodule</span> <span class="no">YourApp</span><span class="o">.</span><span class="n">ExAdmin</span><span class="o">.</span><span class="n">User</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">ExAdmin</span><span class="o">.</span><span class="n">Register</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">register_resource</span> <span class="no">YourApp</span><span class="o">.</span><span class="n">User</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">index</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="n">selectable_column</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">column</span> <span class="ss">:id</span>
</span></span><span class="line"><span class="cl">      <span class="n">column</span> <span class="ss">:name</span>
</span></span><span class="line"><span class="cl">      <span class="n">column</span> <span class="ss">:email</span>
</span></span><span class="line"><span class="cl">      <span class="n">column</span> <span class="ss">:last_sign_in_at</span>
</span></span><span class="line"><span class="cl">      <span class="n">column</span> <span class="ss">:last_sign_in_ip</span>
</span></span><span class="line"><span class="cl">      <span class="n">column</span> <span class="ss">:sign_in_count</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">show</span> <span class="n">_user</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="n">attributes_table</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">        <span class="n">row</span> <span class="ss">:id</span>
</span></span><span class="line"><span class="cl">        <span class="n">row</span> <span class="ss">:name</span>
</span></span><span class="line"><span class="cl">        <span class="n">row</span> <span class="ss">:email</span>
</span></span><span class="line"><span class="cl">        <span class="n">row</span> <span class="ss">:reset_password_token</span>
</span></span><span class="line"><span class="cl">        <span class="n">row</span> <span class="ss">:reset_password_sent_at</span>
</span></span><span class="line"><span class="cl">        <span class="n">row</span> <span class="ss">:locked_at</span>
</span></span><span class="line"><span class="cl">        <span class="n">row</span> <span class="ss">:unlock_token</span>
</span></span><span class="line"><span class="cl">        <span class="n">row</span> <span class="ss">:sign_in_count</span>
</span></span><span class="line"><span class="cl">        <span class="n">row</span> <span class="ss">:current_sign_in_at</span>
</span></span><span class="line"><span class="cl">        <span class="n">row</span> <span class="ss">:last_sign_in_at</span>
</span></span><span class="line"><span class="cl">        <span class="n">row</span> <span class="ss">:current_sign_in_ip</span>
</span></span><span class="line"><span class="cl">        <span class="n">row</span> <span class="ss">:last_sign_in_ip</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">form</span> <span class="n">user</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="n">inputs</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">        <span class="n">input</span> <span class="n">user</span><span class="p">,</span> <span class="ss">:name</span>
</span></span><span class="line"><span class="cl">        <span class="n">input</span> <span class="n">user</span><span class="p">,</span> <span class="ss">:email</span>
</span></span><span class="line"><span class="cl">        <span class="n">input</span> <span class="n">user</span><span class="p">,</span> <span class="ss">:password</span><span class="p">,</span> <span class="ss">type</span><span class="p">:</span> <span class="ss">:password</span>
</span></span><span class="line"><span class="cl">        <span class="n">input</span> <span class="n">user</span><span class="p">,</span> <span class="ss">:password_confirmation</span><span class="p">,</span> <span class="ss">type</span><span class="p">:</span> <span class="ss">:password</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="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>Sim, isso é assustadoramente similar ao DSL do ActiveAdmin. Parabéns para o time responsável — e isso mostra bem como o Elixir é adequado para Domain Specific Languages, se você curte isso.</p>
<p>Se você seguiu as instruções do Coherence, ele pede para adicionar um pipeline <code>:protected</code> (um conjunto de plugs) para suas rotas protegidas. Por ora, você pode adicionar a rota <code>/admin</code> para passar por esse pipeline. Para os não iniciados, um &ldquo;plug&rdquo; é similar em conceito a um app Rack, ou mais especificamente, a um middleware do Rails. Mas no Rails temos apenas um pipeline de middlewares. No Phoenix podemos configurar múltiplos pipelines para diferentes conjuntos de rotas (browser e api, por exemplo).</p>
<p>Então podemos adicionar o seguinte ao <code>web/router.ex</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="o">...</span>
</span></span><span class="line"><span class="cl"><span class="n">scope</span> <span class="s2">&#34;/admin&#34;</span><span class="p">,</span> <span class="no">ExAdmin</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">pipe_through</span> <span class="ss">:protected</span>
</span></span><span class="line"><span class="cl">  <span class="n">admin_routes</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>Com essas configurações simples no lugar, você vai terminar com algo assim:</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/574/Screen_Shot_2016-12-06_at_17.56.17.png" alt="Ex Admin"  loading="lazy" /></p>
<p>E se ainda não estiver convencido, que tal mudar para o tema antigo deles?</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/575/Screen_Shot_2016-12-06_at_17.56.25.png" alt="ActiveAdmin knockoff"  loading="lazy" /></p>
<p>Caramba! Me sinto em casa, embora eu prefira bastante o tema novo. Mas você poderia substituir seu app baseado em ActiveAdmin por este e seus usuários dificilmente vão notar as pequenas diferenças na interface. O comportamento é basicamente o mesmo.</p>
<p>Se ainda tiver dúvidas sobre como configurar o ExAdmin corretamente, confira o projeto <a href="https://github.com/smpallen99/contact_demo"target="_blank" rel="noopener">Contact Demo</a> deles, onde você encontra um exemplo real.</p>
<h3>Costurando um papel de Admin simples<span class="hx:absolute hx:-mt-20" id="costurando-um-papel-de-admin-simples"></span>
    <a href="#costurando-um-papel-de-admin-simples" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Obviamente, não queremos que todos os usuários autenticados tenham acesso à seção de Admin.</p>
<p>Podemos adicionar um campo boolean simples na tabela de usuários para indicar se um usuário é admin ou não. Você pode alterar sua migration 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="o">...</span>
</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">:users</span><span class="p">,</span> <span class="ss">primary_key</span><span class="p">:</span> <span class="kp">false</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">
</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">:email</span><span class="p">,</span> <span class="ss">:string</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span><span class="line"><span class="cl">    <span class="n">add</span> <span class="ss">:admin</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 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></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 você pode configurar o arquivo <code>priv/repos/seeds.exs</code> para criar 2 usuários, um admin e um guest:</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">YourApp</span><span class="o">.</span><span class="n">Repo</span><span class="o">.</span><span class="n">delete_all</span> <span class="no">YourApp</span><span class="o">.</span><span class="n">User</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="no">YourApp</span><span class="o">.</span><span class="n">User</span><span class="o">.</span><span class="n">changeset</span><span class="p">(</span><span class="o">%</span><span class="no">YourApp</span><span class="o">.</span><span class="n">User</span><span class="p">{},</span> <span class="sx">%{name: &#34;Administrator&#34;, email: &#34;admin@example.org&#34;, password: &#34;password&#34;, password_confirmation: &#34;password&#34;, admin: true}</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="o">|&gt;</span> <span class="no">YourApp</span><span class="o">.</span><span class="n">Repo</span><span class="o">.</span><span class="n">insert!</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="no">YourApp</span><span class="o">.</span><span class="n">User</span><span class="o">.</span><span class="n">changeset</span><span class="p">(</span><span class="o">%</span><span class="no">YourApp</span><span class="o">.</span><span class="n">User</span><span class="p">{},</span> <span class="sx">%{name: &#34;Guest&#34;, email: &#34;guest@example.org&#34;, password: &#34;password&#34;, password_confirmation: &#34;password&#34;, admin: false}</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="o">|&gt;</span> <span class="no">YourApp</span><span class="o">.</span><span class="n">Repo</span><span class="o">.</span><span class="n">insert!</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 é só um exercício, você pode dropar o banco e recriar assim: <code>mix do ecto.drop, ecto.setup</code>.</p>
<p>O Coherence cuida da <strong>autenticação</strong>, mas precisamos cuidar da <strong>autorização</strong>. Você vai encontrar muitos exemplos online de algo semelhante ao Pundit do Rails, como o <a href="https://github.com/schrockwell/bodyguard"target="_blank" rel="noopener">Bodyguard</a>. Mas neste post vou me limitar a um Plug simples e criar um novo pipeline no Router.</p>
<p>Precisamos criar <code>lib/your_app/plugs/authorized.ex</code> com o seguinte conteúdo:</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">YourApp</span><span class="o">.</span><span class="n">Plugs</span><span class="o">.</span><span class="n">Authorized</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="vi">@behaviour</span> <span class="no">Plug</span>
</span></span><span class="line"><span class="cl">
</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 class="n">import</span> <span class="no">Phoenix</span><span class="o">.</span><span class="n">Controller</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">default</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="n">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">call</span><span class="p">(</span><span class="sx">%{assigns: %{current_user: current_user}}</span> <span class="o">=</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="k">if</span> <span class="n">current_user</span><span class="o">.</span><span class="n">admin</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></span><span class="line"><span class="cl">        <span class="o">|&gt;</span> <span class="n">flash_and_redirect</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">call</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">conn</span>
</span></span><span class="line"><span class="cl">      <span class="o">|&gt;</span> <span class="n">flash_and_redirect</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">flash_and_redirect</span><span class="p">(</span><span class="n">conn</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="o">|&gt;</span> <span class="n">put_flash</span><span class="p">(</span><span class="ss">:error</span><span class="p">,</span> <span class="s2">&#34;You do not have the proper authorization to do that&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="o">|&gt;</span> <span class="n">redirect</span><span class="p">(</span><span class="ss">to</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="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></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 um usuário faz login, o Coherence coloca a estrutura do usuário autenticado no <code>conn</code> (uma estrutura Plug.Conn), então podemos fazer pattern match a partir daí.</p>
<p>Agora precisamos criar o pipeline do router em <code>web/router.ex</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="o">...</span>
</span></span><span class="line"><span class="cl"><span class="n">pipeline</span> <span class="ss">:protected_admin</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="n">plug</span> <span class="ss">: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="n">plug</span> <span class="no">Coherence</span><span class="o">.</span><span class="n">Authentication</span><span class="o">.</span><span class="n">Session</span><span class="p">,</span> <span class="kp">protected</span><span class="p">:</span> <span class="kp">true</span>
</span></span><span class="line"><span class="cl">  <span class="n">plug</span> <span class="no">YourApp</span><span class="o">.</span><span class="n">Plugs</span><span class="o">.</span><span class="n">Authorized</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="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">pipe_through</span> <span class="ss">:protected_admin</span>
</span></span><span class="line"><span class="cl">  <span class="n">coherence_routes</span> <span class="ss">:protected_admin</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;/admin&#34;</span><span class="p">,</span> <span class="no">ExAdmin</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">pipe_through</span> <span class="ss">:protected_admin</span>
</span></span><span class="line"><span class="cl">  <span class="n">admin_routes</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>O pipeline <code>:protected_admin</code> é exatamente igual ao <code>:protected</code>, mas adicionamos o plug <code>YourApp.Plugs.Authorized</code> recém-criado no final. Depois alteramos o escopo <code>/admin</code> para passar por esse novo pipeline.</p>
<p>E é isso. Se você logar com o usuário <code>guest@example.org</code>, ele será redirecionado para a homepage com uma mensagem dizendo que não está autorizado. Se logar com <code>admin@example.org</code>, terá acesso à interface do ExAdmin em <code>/admin</code>.</p>
<h3>Concluindo<span class="hx:absolute hx:-mt-20" id="concluindo"></span>
    <a href="#concluindo" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Mesmo que agora seja relativamente simples adicionar Authentication, Administration e Authorization básica, não se engane — a curva de aprendizado ainda é íngreme, mesmo que você seja um desenvolvedor Rails experiente.</p>
<p>Por causa do que está por baixo — a arquitetura OTP, os conceitos de Applications, Supervisors, Workers, etc — não é imediatamente simples entender o que está <strong>realmente</strong> acontecendo. Se não tomar cuidado, bibliotecas como Coherence ou ExAdmin vão te fazer achar que é tão simples quanto Rails.</p>
<p>E não é assim. Elixir é uma criatura completamente diferente. E digo isso de forma alguma de maneira negativa — pelo contrário. Ele é feito para sistemas altamente confiáveis e distribuídos, e exige muito mais conhecimento, paciência e treinamento do programador.</p>
<p>Por outro lado, exatamente porque bibliotecas como o Coherence facilitam bastante o começo, você pode se tornar mais motivado a colocar algo em funcionamento e depois investir mais tempo realmente <strong>entendendo</strong> o que está acontecendo por baixo. Por isso a recomendação é: coloque a mão na massa, consiga aquela gratificação instantânea de ver algo rodando, e então vá refinando seu conhecimento. Vai ser muito mais recompensador assim.</p>
<p>Não vejo o Phoenix como um simples substituto do Rails. Isso seria pouco ambicioso. Vejo ele mais como uma peça para tornar o Elixir o conjunto de tecnologias mais adequado para construir <strong>sistemas altamente escaláveis, altamente confiáveis e altamente distribuídos</strong>. Parar em aplicações web simples não faria jus ao potencial do Elixir.</p>
]]></content:encoded><category>elixir-e-erlang</category><category>backend</category><category>seguranca</category></item><item><title>Operador Pipe do Elixir para Ruby: Chainable Methods</title><link>https://akitaonrails.github.io/2016/02/18/operador-pipe-do-elixir-para-ruby-chainable-methods/</link><guid isPermaLink="true">https://akitaonrails.github.io/2016/02/18/operador-pipe-do-elixir-para-ruby-chainable-methods/</guid><pubDate>Thu, 18 Feb 2016 16:28:00 -0200</pubDate><description>&lt;p&gt;Rolaram &lt;a href="http://blog.molawson.com/elixir-pipes-in-ruby/"target="_blank" rel="noopener"&gt;discussões&lt;/a&gt; &lt;a href="https://gist.github.com/pcreux/2f87847e5e4aad37db02"target="_blank" rel="noopener"&gt;recentes&lt;/a&gt; sobre como seria legal ter algo parecido com o incrível Pipe Operator do Elixir no Ruby.&lt;/p&gt;
&lt;p&gt;Se você não sabe o que é o &amp;ldquo;Pipe Operator&amp;rdquo; no Elixir, olha esse código aqui:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;100_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="n"&gt;odd?&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;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"&gt;
&lt;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"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;É feio, todo mundo sabe. Numa linguagem orientada a objetos como Ruby, a gente faria algo assim:&lt;/p&gt;</description><content:encoded><![CDATA[<p>Rolaram <a href="http://blog.molawson.com/elixir-pipes-in-ruby/"target="_blank" rel="noopener">discussões</a> <a href="https://gist.github.com/pcreux/2f87847e5e4aad37db02"target="_blank" rel="noopener">recentes</a> sobre como seria legal ter algo parecido com o incrível Pipe Operator do Elixir no Ruby.</p>
<p>Se você não sabe o que é o &ldquo;Pipe Operator&rdquo; no Elixir, olha esse código aqui:</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">Enum</span><span class="o">.</span><span class="n">sum</span><span class="p">(</span><span class="no">Enum</span><span class="o">.</span><span class="n">filter</span><span class="p">(</span><span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="mi">1</span><span class="o">..</span><span class="mi">100_000</span><span class="p">,</span> <span class="o">&amp;</span><span class="p">(</span><span class="o">&amp;</span><span class="mi">1</span> <span class="o">*</span> <span class="mi">3</span><span class="p">)),</span> <span class="n">odd?</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>É feio, todo mundo sabe. Numa linguagem orientada a objetos como Ruby, a gente faria 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="p">(</span><span class="mi">1</span><span class="o">..</span><span class="mi">100_000</span><span class="p">)</span><span class="o">.</span>
</span></span><span class="line"><span class="cl">  <span class="n">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">i</span><span class="o">|</span> <span class="n">i</span> <span class="o">*</span> <span class="mi">3</span> <span class="p">}</span><span class="o">.</span>
</span></span><span class="line"><span class="cl">  <span class="nb">select</span><span class="p">(</span><span class="o">&amp;</span><span class="ss">:odd?</span><span class="p">)</span><span class="o">.</span>
</span></span><span class="line"><span class="cl">  <span class="n">reduce</span><span class="p">(</span><span class="o">&amp;</span><span class="ss">:+</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>Só que Elixir não tem objetos, só funções. Então como escrever código de forma mais elegante? A solução apareceu na forma do tal &ldquo;Pipe Operator&rdquo;, que pega o último valor retornado e passa ele como primeiro argumento da próxima chamada de funçã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="mi">1</span><span class="o">..</span><span class="mi">100_000</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="no">Stream</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="o">&amp;</span><span class="mi">1</span> <span class="o">*</span> <span class="mi">3</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="no">Stream</span><span class="o">.</span><span class="n">filter</span><span class="p">(</span><span class="n">odd?</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">sum</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 Ruby e Elixir &ldquo;parecem&rdquo; a mesma coisa quando conseguimos &ldquo;encadear&rdquo; métodos. No mundo Ruby a gente não tem a &ldquo;necessidade&rdquo; de um operador desses. Mas seria bacana ter um mecanismo para deixar nosso código mais expressivo, mais testável, mais legível. Por exemplo, imagina escrever 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="p">(</span><span class="mi">1</span><span class="o">..</span><span class="mi">100_000</span><span class="p">)</span><span class="o">.</span>
</span></span><span class="line"><span class="cl">  <span class="n">multiple_each_element_by_three</span><span class="o">.</span>
</span></span><span class="line"><span class="cl">  <span class="n">filter_out_odd_elements</span><span class="o">.</span>
</span></span><span class="line"><span class="cl">  <span class="n">give_the_sum_of_all_elements</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, esse é um exemplo bem limitado com nomes de métodos péssimos. Mas se você pegar o artigo do Mo Lawson que linkei acima, fica bem mais interessante:</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">keywords</span>
</span></span><span class="line"><span class="cl">  <span class="o">.</span><span class="n">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">kw</span><span class="o">|</span> <span class="n">kw</span><span class="o">.</span><span class="n">gsub</span><span class="p">(</span><span class="sr">/(^[^[:alpha:]]+|[^[:alpha:]]+$)/</span><span class="p">,</span> <span class="s1">&#39;&#39;</span><span class="p">)</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="o">.</span><span class="n">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">kw</span><span class="o">|</span> <span class="no">LegacySpanishCorrector</span><span class="o">.</span><span class="n">new</span><span class="o">.</span><span class="n">correct</span><span class="p">(</span><span class="n">kw</span><span class="p">)</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="o">.</span><span class="n">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">kw</span><span class="o">|</span> <span class="n">kw</span><span class="o">.</span><span class="n">gsub</span><span class="p">(</span><span class="sr">/[^[:alpha:]\d&#39;_\-\/]/</span><span class="p">,</span> <span class="s1">&#39;&#39;</span><span class="p">)</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="o">.</span><span class="n">reject</span> <span class="p">{</span> <span class="o">|</span><span class="n">kw</span><span class="o">|</span> <span class="no">STOP_WORDS</span><span class="o">.</span><span class="n">include?</span><span class="p">(</span><span class="n">kw</span><span class="p">)</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>Ruby permite encadear métodos de Enumerable um atrás do outro, transformando a lista inicial de keywords em &ldquo;alguma coisa&rdquo; que fica bem difícil de inferir só olhando para o código.</p>
<p>E essa outra versão, o que acha?</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">KeywordNormalizer</span>
</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">keywords</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="no">Collection</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">keywords</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="o">.</span><span class="n">strip_outer_punctuation</span>
</span></span><span class="line"><span class="cl">      <span class="o">.</span><span class="n">fix_spanish</span>
</span></span><span class="line"><span class="cl">      <span class="o">.</span><span class="n">clean_inner_punctuation</span>
</span></span><span class="line"><span class="cl">      <span class="o">.</span><span class="n">remove_stop_words</span>
</span></span><span class="line"><span class="cl">      <span class="o">.</span><span class="n">to_a</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"># ...</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>É aonde ele chega no fim do artigo: muito mais legível, e cada método isolado fica testável via unit test, resultando em código mais robusto.</p>
<p>A ideia desse post é apresentar minha nova gem <a href="https://rubygems.org/gems/chainable_methods"target="_blank" rel="noopener">&ldquo;Chainable Methods&rdquo;</a>. O <a href="https://github.com/akitaonrails/chainable_methods"target="_blank" rel="noopener">código-fonte</a> está no Github, como sempre, então por favor contribua.</p>
<p>Minha gem permite escrever o último código do Lawson 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="no">KeywordNormalizer</span>
</span></span><span class="line"><span class="cl">  <span class="o">.</span><span class="n">chain_from</span><span class="p">(</span><span class="n">keywords</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="o">.</span><span class="n">strip_outer_punctuation</span>
</span></span><span class="line"><span class="cl">  <span class="o">.</span><span class="n">fix_spanish</span>
</span></span><span class="line"><span class="cl">  <span class="o">.</span><span class="n">clean_inner_punctuation</span>
</span></span><span class="line"><span class="cl">  <span class="o">.</span><span class="n">remove_stop_words</span>
</span></span><span class="line"><span class="cl">  <span class="o">.</span><span class="n">to_a</span>
</span></span><span class="line"><span class="cl">  <span class="o">.</span><span class="n">unwrap</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ê adiciona o <tt>chainable_methods</tt> no seu Gemfile como sempre (já sabe o procedimento), e aí pode escrever o módulo do Lawson desse jeito:</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">KeywordNormalizer</span>
</span></span><span class="line"><span class="cl">  <span class="kp">extend</span> <span class="no">ChainableMethods</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">strip_outer_punctuation</span><span class="p">(</span><span class="n">array</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">array</span><span class="o">.</span><span class="n">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">el</span><span class="o">|</span> <span class="n">el</span><span class="o">.</span><span class="n">gsub</span><span class="p">(</span><span class="sr">/(^[^[:alpha:]]+|[^[:alpha:]]+$)/</span><span class="p">,</span> <span class="s1">&#39;&#39;</span><span class="p">)</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="nc">self</span><span class="o">.</span><span class="nf">fix_spanish</span><span class="p">(</span><span class="n">array</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">array</span><span class="o">.</span><span class="n">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">el</span><span class="o">|</span> <span class="no">LegacySpanishCorrector</span><span class="o">.</span><span class="n">new</span><span class="o">.</span><span class="n">correct</span><span class="p">(</span><span class="n">el</span><span class="p">)</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="nc">self</span><span class="o">.</span><span class="nf">clean_inner_punctuation</span><span class="p">(</span><span class="n">array</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">array</span><span class="o">.</span><span class="n">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">el</span><span class="o">|</span> <span class="n">el</span><span class="o">.</span><span class="n">gsub</span><span class="p">(</span><span class="sr">/[^[:alpha:]\d&#39;_\-\/]/</span><span class="p">,</span> <span class="s1">&#39;&#39;</span><span class="p">)</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="nc">self</span><span class="o">.</span><span class="nf">remove_stop_words</span><span class="p">(</span><span class="n">array</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">array</span><span class="o">.</span><span class="n">reject</span> <span class="p">{</span> <span class="o">|</span><span class="n">el</span><span class="o">|</span> <span class="no">STOP_WORDS</span><span class="o">.</span><span class="n">include?</span><span class="p">(</span><span class="n">el</span><span class="p">)</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, agora você pode encadear tudo como mostrei antes. O padrão é:</p>
<ol>
<li>Escreva um Module com métodos de classe que recebam pelo menos um argumento e que extendam o módulo &lsquo;ChainableMethods&rsquo;:</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="k">module</span> <span class="nn">MyModule</span>
</span></span><span class="line"><span class="cl">  <span class="kp">extend</span> <span class="no">ChainableMethods</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">method_a</span><span class="p">(</span><span class="n">argument1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># ...</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="nc">self</span><span class="o">.</span><span class="nf">method_b</span><span class="p">(</span><span class="n">argument1</span><span class="p">,</span> <span class="n">argument2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># ...</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="nc">self</span><span class="o">.</span><span class="nf">method_c</span><span class="p">(</span><span class="n">argument1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># yield a block passing the received argument</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>Envolva um estado inicial que será passado como primeiro argumento do primeiro método:</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">some_initial_state</span> <span class="o">=</span> <span class="s2">&#34;Hello World&#34;</span>
</span></span><span class="line"><span class="cl"><span class="no">MyModule</span>
</span></span><span class="line"><span class="cl">  <span class="o">.</span><span class="n">chain_from</span><span class="p">(</span><span class="n">some_initial_state</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># ...</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="3">
<li>Encadeie quantos métodos quiser do módulo ou métodos que o estado retornado reconheça:</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="no">MyModule</span>
</span></span><span class="line"><span class="cl">  <span class="o">.</span><span class="n">chain_from</span><span class="p">(</span><span class="n">some_initial_state</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="o">.</span><span class="n">upcase</span>
</span></span><span class="line"><span class="cl">  <span class="o">.</span><span class="n">method_a</span>
</span></span><span class="line"><span class="cl">  <span class="o">.</span><span class="n">method_b</span><span class="p">(</span><span class="n">argument2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="o">.</span><span class="n">method_c</span> <span class="p">{</span> <span class="o">|</span><span class="n">foo</span><span class="o">|</span> <span class="n">foo</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="o">.</span><span class="n">split</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="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="o">.</span><span class="n">unwrap</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 não precisamos passar o primeiro argumento para os métodos dentro do módulo &lsquo;MyModule&rsquo;. Ele pega o resultado da última chamada automaticamente.</p>
<ol start="4">
<li>Não esqueça de chamar <tt>#unwrap</tt> como última chamada para obter o resultado final da cadeia.</li>
</ol>
<h3>Um Experimento<span class="hx:absolute hx:-mt-20" id="um-experimento"></span>
    <a href="#um-experimento" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>E é isso! Isolei esse comportamento apenas em módulos que explicitamente extendem o módulo &lsquo;ChainableMethods&rsquo;, em vez de habilitar automagicamente no nível de BasicObject como muitos pensariam de primeira, porque queremos evitar um &lsquo;method_missing&rsquo; global solto por aí sem controle.</p>
<p>Esse comportamento usa &lsquo;method_missing&rsquo;, então não vai ser rápido num benchmark sintético contra uma chamada direta de método, por razões óbvias. O propósito aqui é expressividade, não velocidade. Tenha isso em mente.</p>
<p>O caso de uso é: sempre que você tiver algum tipo de transformação, vai querer uma cadeia de funções isoladas e testáveis via unit test. E é assim que dá para conseguir isso sem muita dor de cabeça.</p>
<p>Isso é um experimento. Como estou usando &lsquo;method_missing&rsquo;, podem rolar efeitos colaterais que ainda não enxerguei, então por favor me avise nas <a href="https://github.com/akitaonrails/chainable_methods/issues"target="_blank" rel="noopener">Github Issues</a> e mande feedback se ajudou em algum projeto.</p>
<p>Pull Requests são muito bem-vindos!</p>
]]></content:encoded><category>ruby</category><category>elixir-e-erlang</category><category>linguagens-de-programacao</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>Ping Pong de Erlang (tut15) em Clojure e Elixir</title><link>https://akitaonrails.github.io/2015/12/09/ping-pong-de-erlang-tut15-em-clojure-e-elixir/</link><guid isPermaLink="true">https://akitaonrails.github.io/2015/12/09/ping-pong-de-erlang-tut15-em-clojure-e-elixir/</guid><pubDate>Wed, 09 Dec 2015 14:42:00 -0200</pubDate><description>&lt;p&gt;Esse vai ser um post bem curto, só porque achei divertido. Eu estava lendo &lt;a href="http://blog.paralleluniverse.co/2013/05/02/quasar-pulsar/"target="_blank" rel="noopener"&gt;esse artigo bem esclarecedor&lt;/a&gt; sobre o Quasar/Pulsar do Clojure comparado ao Erlang e como eles estão tentando tapar os buracos das limitações da JVM.&lt;/p&gt;
&lt;p&gt;Quando você está aprendendo Erlang pela documentação oficial, a primeira coisa que você constrói nos &lt;a href="http://www.erlang.org/doc/getting_started/conc_prog.html#id67347"target="_blank" rel="noopener"&gt;capítulos sobre Processos&lt;/a&gt; é um código bem simples de Ping Pong que se parece com isso:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-erlang" data-lang="erlang"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="ni"&gt;module&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tut15&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="ni"&gt;export&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ping&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pong&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;ping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Pong_PID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;Pong_PID&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;finished&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nn"&gt;io&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;ping finished&lt;/span&gt;&lt;span class="si"&gt;~n&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;ping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;N&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Pong_PID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;Pong_PID&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ping&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;()},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;receive&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;pong&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nn"&gt;io&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Ping received pong&lt;/span&gt;&lt;span class="si"&gt;~n&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;N&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Pong_PID&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;pong&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;receive&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;finished&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nn"&gt;io&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Pong finished&lt;/span&gt;&lt;span class="si"&gt;~n&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ping&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Ping_PID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nn"&gt;io&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Pong received ping&lt;/span&gt;&lt;span class="si"&gt;~n&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;Ping_PID&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;pong&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;pong&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;Pong_PID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tut15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pong&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tut15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ping&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Pong_PID&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;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"&gt;
&lt;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"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Não é bonito, tem cara de Prolog. O artigo sobre Clojure afirma o quão perto eles chegaram com lightweight threads (green threads de verdade) e esse é o mesmo exercício feito em Clojure:&lt;/p&gt;</description><content:encoded><![CDATA[<p>Esse vai ser um post bem curto, só porque achei divertido. Eu estava lendo <a href="http://blog.paralleluniverse.co/2013/05/02/quasar-pulsar/"target="_blank" rel="noopener">esse artigo bem esclarecedor</a> sobre o Quasar/Pulsar do Clojure comparado ao Erlang e como eles estão tentando tapar os buracos das limitações da JVM.</p>
<p>Quando você está aprendendo Erlang pela documentação oficial, a primeira coisa que você constrói nos <a href="http://www.erlang.org/doc/getting_started/conc_prog.html#id67347"target="_blank" rel="noopener">capítulos sobre Processos</a> é um código bem simples de Ping Pong 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-erlang" data-lang="erlang"><span class="line"><span class="cl"><span class="p">-</span><span class="ni">module</span><span class="p">(</span><span class="n">tut15</span><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="ni">export</span><span class="p">([</span><span class="n">start</span><span class="o">/</span><span class="mi">0</span><span class="p">,</span> <span class="n">ping</span><span class="o">/</span><span class="mi">2</span><span class="p">,</span> <span class="n">pong</span><span class="o">/</span><span class="mi">0</span><span class="p">]).</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">ping</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nv">Pong_PID</span><span class="p">)</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nv">Pong_PID</span> <span class="o">!</span> <span class="n">finished</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;ping finished</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></span><span class="line"><span class="cl"><span class="nf">ping</span><span class="p">(</span><span class="nv">N</span><span class="p">,</span> <span class="nv">Pong_PID</span><span class="p">)</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nv">Pong_PID</span> <span class="o">!</span> <span class="p">{</span><span class="n">ping</span><span class="p">,</span> <span class="n">self</span><span class="p">()},</span>
</span></span><span class="line"><span class="cl">    <span class="k">receive</span>
</span></span><span class="line"><span class="cl">        <span class="n">pong</span> <span class="o">-&gt;</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;Ping received pong</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="k">end</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">ping</span><span class="p">(</span><span class="nv">N</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span> <span class="nv">Pong_PID</span><span class="p">).</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">pong</span><span class="p">()</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="k">receive</span>
</span></span><span class="line"><span class="cl">        <span class="n">finished</span> <span class="o">-&gt;</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;Pong finished</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="p">{</span><span class="n">ping</span><span class="p">,</span> <span class="nv">Ping_PID</span><span class="p">}</span> <span class="o">-&gt;</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;Pong received ping</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">Ping_PID</span> <span class="o">!</span> <span class="n">pong</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">pong</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></span><span class="line"><span class="cl"><span class="nf">start</span><span class="p">()</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nv">Pong_PID</span> <span class="o">=</span> <span class="nb">spawn</span><span class="p">(</span><span class="n">tut15</span><span class="p">,</span> <span class="n">pong</span><span class="p">,</span> <span class="p">[]),</span>
</span></span><span class="line"><span class="cl">    <span class="nb">spawn</span><span class="p">(</span><span class="n">tut15</span><span class="p">,</span> <span class="n">ping</span><span class="p">,</span> <span class="p">[</span><span class="mi">3</span><span class="p">,</span> <span class="nv">Pong_PID</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>Não é bonito, tem cara de Prolog. O artigo sobre Clojure afirma o quão perto eles chegaram com lightweight threads (green threads de verdade) e esse é o mesmo exercício feito em Clojure:</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-lisp" data-lang="lisp"><span class="line"><span class="cl"><span class="p">(</span><span class="nv">defsfn</span> <span class="nv">pong</span> <span class="nv">[]</span>
</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="k">if</span> <span class="p">(</span><span class="nv">==</span> <span class="nv">n</span> <span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="nb">do</span>
</span></span><span class="line"><span class="cl">      <span class="p">(</span><span class="nv">!</span> <span class="ss">:pong</span> <span class="ss">:finished</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">(</span><span class="nv">println</span> <span class="s">&#34;ping finished&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="nb">do</span>
</span></span><span class="line"><span class="cl">      <span class="p">(</span><span class="nv">!</span> <span class="ss">:pong</span> <span class="nv">[:ping</span> <span class="nv">@self]</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">(</span><span class="nv">receive</span>
</span></span><span class="line"><span class="cl">       <span class="ss">:pong</span> <span class="p">(</span><span class="nv">println</span> <span class="s">&#34;Ping received pong&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">      <span class="p">(</span><span class="nv">recur</span> <span class="p">(</span><span class="nv">dec</span> <span class="nv">n</span><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="nv">defsfn</span> <span class="nv">pong</span> <span class="nv">[]</span>
</span></span><span class="line"><span class="cl">  <span class="p">(</span><span class="nv">receive</span>
</span></span><span class="line"><span class="cl">   <span class="ss">:finished</span> <span class="p">(</span><span class="nv">println</span> <span class="s">&#34;Pong finished&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">   <span class="nv">[:ping</span> <span class="nv">ping]</span> <span class="p">(</span><span class="nb">do</span>
</span></span><span class="line"><span class="cl">                  <span class="p">(</span><span class="nv">println</span> <span class="s">&#34;Pong received ping&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                  <span class="p">(</span><span class="nv">!</span> <span class="nv">ping</span> <span class="ss">:pong</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                  <span class="p">(</span><span class="nv">recur</span><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="nv">defn</span> <span class="nv">-main</span> <span class="nv">[]</span>
</span></span><span class="line"><span class="cl">  <span class="p">(</span><span class="nv">register</span> <span class="ss">:pong</span> <span class="p">(</span><span class="nv">spawn</span> <span class="nv">pong</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">  <span class="p">(</span><span class="nv">spawn</span> <span class="nv">ping</span> <span class="mi">3</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="ss">:ok</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>Quem curte a estética Lisp de programar diretamente na representação da AST através da estrutura de blocos baseados em parênteses vai achar isso muito bonito.</p>
<p>Eu particularmente passei anos olhando para código desse tipo (Common Lisp, Elisp, Scheme, etc) e ainda não consigo me acostumar. Quando você tem um editor competente como o Emacs, que lida com o casamento adequado de parênteses, fica mais fácil, sim, mas eu ainda não consigo achar graça nesse tipo de sintaxe meio hackeada.</p>
<p>Elixir vai muito além de só uma sintaxe nova em cima de Erlang, como o ótimo livro <a href="https://pragprog.com/book/cmelixir/metaprogramming-elixir"target="_blank" rel="noopener">Metaprogramming Elixir</a> vai te ensinar. Ele abre toda a AST da BEAM do Erlang através do mecanismo de <a href="http://elixir-lang.org/getting-started/meta/quote-and-unquote.html"target="_blank" rel="noopener">quote/unquote</a>, tornando a programação direta na AST através de <a href="http://elixir-lang.org/getting-started/meta/macros.html"target="_blank" rel="noopener">&ldquo;Macros Higiênicas&rdquo;</a> algo trivial. Você ganha o melhor dos dois mundos: uma sintaxe moderna, bonita e prazerosa, junto com o mesmo poder que uma linguagem Lisp te dá em termos de macros bem comportadas.</p>
<p>Agora, esse é o mesmo exemplo acima, em Elixir:</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">ExPingPongTut15</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">ping</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">pong_pid</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="nb">send</span> <span class="n">pong_pid</span><span class="p">,</span> <span class="ss">:finished</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="p">(</span><span class="s2">&#34;ping finished&#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">ping</span><span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="n">pong_pid</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="nb">send</span> <span class="n">pong_pid</span><span class="p">,</span> <span class="p">{</span><span class="ss">:ping</span><span class="p">,</span> <span class="nb">self</span><span class="p">}</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="ss">:pong</span> <span class="o">-&gt;</span> <span class="no">IO</span><span class="o">.</span><span class="n">puts</span><span class="p">(</span><span class="s2">&#34;Ping received pong&#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="n">ping</span><span class="p">(</span><span class="n">n</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span> <span class="n">pong_pid</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">pong</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="ss">:finished</span> <span class="o">-&gt;</span> <span class="no">IO</span><span class="o">.</span><span class="n">puts</span><span class="p">(</span><span class="s2">&#34;Pong finished&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span><span class="ss">:ping</span><span class="p">,</span> <span class="n">ping_pid</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="p">(</span><span class="s2">&#34;Pong received ping&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="nb">send</span> <span class="n">ping_pid</span><span class="p">,</span> <span class="ss">:pong</span>
</span></span><span class="line"><span class="cl">        <span class="n">pong</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">start</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">pong_pid</span> <span class="o">=</span> <span class="n">spawn</span><span class="p">(</span><span class="n">fn</span> <span class="o">-&gt;</span> <span class="n">pong</span> <span class="k">end</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">spawn</span><span class="p">(</span><span class="n">fn</span> <span class="o">-&gt;</span> <span class="n">ping</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="n">pong_pid</span><span class="p">)</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><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 causa do poder do pattern matching na assinatura dos argumentos das funções, dá pra definir 2 funções separadas, evitando o &ldquo;if&rdquo; como no exemplo em Clojure. Claro, como é Clojure e tem um sistema completo de macros, e como o <a href="https://github.com/clojure/core.match"target="_blank" rel="noopener">core.match</a> tem mecanismos adequados de pattern matching, dá pra emular a mesma coisa através de bibliotecas externas como o <a href="https://github.com/killme2008/defun"target="_blank" rel="noopener">defun</a>.</p>
<p>Esse foi só um exercício simples, espero que tenha jogado alguma luz nas semelhanças básicas entre essas 3 linguagens. E por mais &ldquo;feio&rdquo; que o Erlang possa parecer, eu ainda me sinto mais confortável com as suas esquisitices do que com parênteses aninhados.</p>
]]></content:encoded><category>linguagens-de-programacao</category><category>elixir-e-erlang</category><category>aprendizado</category></item><item><title>Ex Manga Downloadr - Parte 5: Deixando mais robusto!</title><link>https://akitaonrails.github.io/2015/12/06/ex-manga-downloadr-parte-5-deixando-mais-robusto/</link><guid isPermaLink="true">https://akitaonrails.github.io/2015/12/06/ex-manga-downloadr-parte-5-deixando-mais-robusto/</guid><pubDate>Sun, 06 Dec 2015 19:20:00 -0200</pubDate><description>&lt;p&gt;E lá vou eu de novo. Sei que alguns de vocês já podem estar entediados com essa ferramenta, mas como projeto de brincadeira, ainda quero deixar o código bom. Só que tem 2 problemas grandes agora.&lt;/p&gt;
&lt;p&gt;Quando eu testava só com o MangaReader.net como fonte, tudo funcionava quase sem falhas. Mas ao adicionar o MangaFox na &lt;a href="http://www.akitaonrails.com/2015/12/02/ex-manga-downloadr-parte-3-suporte-ao-mangafox"target="_blank" rel="noopener"&gt;Parte 3&lt;/a&gt;, com suas regras mais restritivas contra ferramentas de scrapping como a minha (timeouts mais frequentes, não permitindo muitas conexões da mesma origem, etc), o processo simplesmente quebrava o tempo todo e eu tinha que reiniciar manualmente (as features de retomada que adicionei na &lt;a href="http://www.akitaonrails.com/2015/12/03/ex-manga-downloadr-parte-4-aprendendo-atraves-do-refactoring"target="_blank" rel="noopener"&gt;Parte 4&lt;/a&gt; ajudaram bastante, mas a ferramenta deixou de ser confiável).&lt;/p&gt;</description><content:encoded><![CDATA[<p>E lá vou eu de novo. Sei que alguns de vocês já podem estar entediados com essa ferramenta, mas como projeto de brincadeira, ainda quero deixar o código bom. Só que tem 2 problemas grandes agora.</p>
<p>Quando eu testava só com o MangaReader.net como fonte, tudo funcionava quase sem falhas. Mas ao adicionar o MangaFox na <a href="http://www.akitaonrails.com/2015/12/02/ex-manga-downloadr-parte-3-suporte-ao-mangafox"target="_blank" rel="noopener">Parte 3</a>, com suas regras mais restritivas contra ferramentas de scrapping como a minha (timeouts mais frequentes, não permitindo muitas conexões da mesma origem, etc), o processo simplesmente quebrava o tempo todo e eu tinha que reiniciar manualmente (as features de retomada que adicionei na <a href="http://www.akitaonrails.com/2015/12/03/ex-manga-downloadr-parte-4-aprendendo-atraves-do-refactoring"target="_blank" rel="noopener">Parte 4</a> ajudaram bastante, mas a ferramenta deixou de ser confiável).</p>
<p>Recapitulando, o Workflow só organiza cada passo do processo. Suas funções são parecidas 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">def</span> <span class="nf">process_downloads</span><span class="p">(</span><span class="n">images_list</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="n">images_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_download_image</span><span class="p">(</span><span class="o">&amp;</span><span class="mi">1</span><span class="p">,</span> <span class="n">directory</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="n">directory</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 lida com uma lista grande, faz um map sobre cada elemento mandando para uma função Worker rodar, 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">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="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">: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">@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>Ele retorna uma Task assíncrona esperando por 2 coisas: o Poolboy liberar um processo livre para usar, e a função Worker/GenServer terminar de rodar dentro daquele processo. Como 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</a>, isso é para limitar o número máximo de conexões com a fonte externa. Sem essa restrição, mandando dezenas de milhares de requests assíncronos de uma vez, a fonte externa simplesmente falharia todos.</p>
<p>A primeira coisa para se ter em mente é que um &ldquo;<tt>Task.async/2</tt>&rdquo; se linka com o processo chamador, então se algo der errado, o processo pai morre junto.</p>
<p>A coisa certa a fazer é adicionar um <a href="http://elixir-lang.org/docs/stable/elixir/Task.Supervisor.html"target="_blank" rel="noopener">Task.Supervisor</a> e deixar ele cuidar de cada Task filha. Para fazer isso, basta adicionar o Supervisor na nossa árvore supervisionada em &ldquo;pool_management/supervisor.ex&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">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 class="o">...</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">supervisor</span><span class="p">(</span><span class="no">Task</span><span class="o">.</span><span class="n">Supervisor</span><span class="p">,</span> <span class="o">[[</span><span class="nb">name</span><span class="p">:</span> <span class="no">Fetcher</span><span class="o">.</span><span class="n">TaskSupervisor</span><span class="o">]]</span><span class="p">),</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 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 podemos substituir as chamadas &ldquo;<tt>Task.async/2</tt>&rdquo; por &ldquo;<tt>Task.Supervisor.async(Fetcher.TaskSupervisor, &hellip;)</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="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">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">: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">@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>Isso ainda cria Tasks pelas quais precisamos esperar, e como antes, se a função lá dentro quebrar, ainda derruba o processo principal. Agora minha refatoração chegou num beco sem saída.</p>
<p>Esse é o 2º problema que mencionei no início do artigo: uma <strong>falha no meu design</strong>.</p>
<p>Em vez de só fazer um map sobre cada elemento de uma lista grande, eu deveria ter criado um GenServer baseado em Agent para manter a lista como estado e transformar todo o sistema do Workflow em um novo GenServer supervisionado. Se buscar uma URL quebrasse o GenServer, seu supervisor o reiniciaria e pegaria o próximo elemento da lista.</p>
<p>Mas, como não estou com pique para essa refatoração agora (é tarde de domingo), vou me concentrar num quick fix (sim, uma gambiarra), só para que a função dentro da chamada async não levante exceções.</p>
<h3>OMG! É um bloco Try/Catch!<span class="hx:absolute hx:-mt-20" id="omg-é-um-bloco-trycatch"></span>
    <a href="#omg-%c3%a9-um-bloco-trycatch" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Acontece que tudo o que rodo dentro dos processos do Poolboy são requests HTTP get através do HTTPotion. Felizmente eu já tinha refatorado toda chamada HTTPotion get em uma macro elegante:</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">defmacro</span> <span class="n">fetch</span><span class="p">(</span><span class="n">link</span><span class="p">,</span> <span class="k">do</span><span class="p">:</span> <span class="n">expression</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">quote</span> <span class="k">do</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;Fetching from </span><span class="si">#{</span><span class="n">unquote</span><span class="p">(</span><span class="n">link</span><span class="p">)</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">case</span> <span class="no">HTTPotion</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">unquote</span><span class="p">(</span><span class="n">link</span><span class="p">),</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">http_headers</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="o">%</span><span class="no">HTTPotion</span><span class="o">.</span><span class="n">Response</span><span class="p">{</span> <span class="ss">body</span><span class="p">:</span> <span class="n">body</span><span class="p">,</span> <span class="ss">headers</span><span class="p">:</span> <span class="n">headers</span><span class="p">,</span> <span class="ss">status_code</span><span class="p">:</span> <span class="mi">200</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">:ok</span><span class="p">,</span> <span class="n">body</span> <span class="o">|&gt;</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">gunzip</span><span class="p">(</span><span class="n">headers</span><span class="p">)</span> <span class="o">|&gt;</span> <span class="n">unquote</span><span class="p">(</span><span class="n">expression</span><span class="p">)</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="p">{</span> <span class="ss">:err</span><span class="p">,</span> <span class="s2">&#34;not found&#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><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 só preciso trocar 1 linha nessa macro:</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 class="k">case</span> <span class="no">HTTPotion</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">unquote</span><span class="p">(</span><span class="n">link</span><span class="p">),</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">http_headers</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">case</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">retryable_http_get</span><span class="p">(</span><span class="n">unquote</span><span class="p">(</span><span class="n">link</span><span class="p">))</span> <span class="k">do</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 definir essa nova lógica de retry no módulo principal:</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">ExMangaDownloadr</span> <span class="k">do</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="c1"># vai tentar de novo fetches falhos por mais de 50 vezes, dormindo 1 segundo entre cada retry</span>
</span></span><span class="line"><span class="cl">  <span class="vi">@max_retries</span>  <span class="mi">50</span>
</span></span><span class="line"><span class="cl">  <span class="vi">@time_to_wait_to_fetch_again</span> <span class="mi">1_000</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">retryable_http_get</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="k">raise</span> <span class="s2">&#34;Failed to fetch from </span><span class="si">#{</span><span class="n">url</span><span class="si">}</span><span class="s2"> after </span><span class="si">#{</span><span class="vi">@max_retries</span><span class="si">}</span><span class="s2"> retries.&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">retryable_http_get</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">retries</span> <span class="p">\\</span> <span class="vi">@max_retries</span><span class="p">)</span> <span class="k">when</span> <span class="n">retries</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">try</span> <span class="k">do</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;Fetching from </span><span class="si">#{</span><span class="n">url</span><span class="si">}</span><span class="s2"> for the </span><span class="si">#{</span><span class="vi">@max_retries</span> <span class="o">-</span> <span class="n">retries</span><span class="si">}</span><span class="s2"> time.&#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">HTTPotion</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">http_headers</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="k">case</span> <span class="n">response</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">        <span class="o">%</span><span class="no">HTTPotion</span><span class="o">.</span><span class="n">Response</span><span class="p">{</span> <span class="ss">body</span><span class="p">:</span> <span class="n">_</span><span class="p">,</span> <span class="ss">headers</span><span class="p">:</span> <span class="n">_</span><span class="p">,</span> <span class="ss">status_code</span><span class="p">:</span> <span class="n">status</span> <span class="p">}</span> <span class="k">when</span> <span class="n">status</span> <span class="o">&gt;</span> <span class="mi">499</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">          <span class="k">raise</span> <span class="o">%</span><span class="no">HTTPotion</span><span class="o">.</span><span class="n">HTTPError</span><span class="p">{</span><span class="ss">message</span><span class="p">:</span> <span class="s2">&#34;req_timedout&#34;</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="n">response</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">rescue</span>
</span></span><span class="line"><span class="cl">      <span class="n">error</span> <span class="k">in</span> <span class="no">HTTPotion</span><span class="o">.</span><span class="n">HTTPError</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="k">case</span> <span class="n">error</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">          <span class="o">%</span><span class="no">HTTPotion</span><span class="o">.</span><span class="n">HTTPError</span><span class="p">{</span><span class="ss">message</span><span class="p">:</span> <span class="n">message</span><span class="p">}</span> <span class="k">when</span> <span class="n">message</span> <span class="k">in</span> <span class="vi">@http_errors</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="ss">:timer</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="vi">@time_to_wait_to_fetch_again</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">retryable_http_get</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">retries</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">_</span> <span class="o">-&gt;</span> <span class="k">raise</span> <span class="n">error</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="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>Eu <a href="http://www.akitaonrails.com/2015/12/01/o-obrigatorio-por-que-elixir-visao-pessoal"target="_blank" rel="noopener">afirmei</a> com convicção que em Elixir <strong>não</strong> devemos usar blocos &ldquo;try/catch&rdquo;, mas aí está.</p>
<p>Essa é a consequência da falha no meu design inicial do Workflow. Se eu tivesse codificado o módulo Workflow como um GenServer, com cada lista gerenciada por um Agent, cada chamada HTTPotion que falhasse permitiria ao supervisor reiniciá-la e tentar de novo. Sem precisar recorrer ao código feio do &ldquo;try/catch&rdquo;.</p>
<p>Talvez isso me force a escrever a Parte 6 sendo o código para remover esse &ldquo;try/catch&rdquo; feio depois, então considerem isto uma <strong>Dívida Técnica</strong> para fazer tudo funcionar agora e depois refatorar para pagar a dívida.</p>
<p>As chamadas &ldquo;<tt>HTTPotion.get/2</tt>&rdquo; podem levantar exceções &ldquo;HTTPotion.HTTPError&rdquo;. Estou capturando esses erros por enquanto, casando as mensagens contra uma lista de erros que eu já tinha, dormindo por um certo tempo (só uma heurística para ver se as fontes externas respondem melhor desse jeito) e recurso na própria função através de um número limitado de &ldquo;retries&rdquo;, até chegar a zero, caso em que pode até ser que a conexão de internet esteja caída ou algum outro erro grave do qual não conseguiríamos nos recuperar tão cedo.</p>
<p>Com esse código no lugar, agora até buscar do MangaFox, sem precisar diminuir o POOL_SIZE, roda até o fim, e isso resolve minhas necessidades por agora. Se alguém tiver interesse em sugerir um design de Workflow melhor, baseado em GenServer, eu agradeceria muito um Pull Request.</p>
<p>Abraços.</p>
]]></content:encoded><category>elixir-e-erlang</category><category>engenharia-de-software</category><category>testes</category></item><item><title>Ex Manga Downloadr - Parte 4: Aprendendo Através do Refactoring</title><link>https://akitaonrails.github.io/2015/12/03/ex-manga-downloadr-parte-4-aprendendo-atraves-do-refactoring/</link><guid isPermaLink="true">https://akitaonrails.github.io/2015/12/03/ex-manga-downloadr-parte-4-aprendendo-atraves-do-refactoring/</guid><pubDate>Thu, 03 Dec 2015 17:40:00 -0200</pubDate><description>&lt;p&gt;&lt;a href="http://www.akitaonrails.com/2015/12/02/ex-manga-downloadr-parte-3-suporte-ao-mangafox"target="_blank" rel="noopener"&gt;Ontem adicionei suporte ao Mangafox&lt;/a&gt; na minha ferramenta de download e isso também acabou jogando um pouco de código sujo no meu código que já não estava lá essas coisas. Hora de uma faxina pra valer.&lt;/p&gt;
&lt;p&gt;Você consegue ver tudo o que fiz desde ontem pra limpar a casa através da &lt;a href="https://github.com/akitaonrails/ex_manga_downloadr/compare/59694565592f8a3bea95115b858dd2ddfdc89873...3ae7dd98a8fd7bae47ffd7a24c0c42a2c3777fad"target="_blank" rel="noopener"&gt;excelente página de compare do Github&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Antes de mais nada: agora a escolha de ter adicionado uma quantidade razoável de testes vai pagar dividendos. Neste refactoring eu mudei assinaturas de funções, formatos de resposta, movi uma boa quantidade de código de lugar, e sem os testes essa empreitada teria me tomado o dia inteiro ou mais, deixando o esforço de refactor questionável desde o início.&lt;/p&gt;</description><content:encoded><![CDATA[<p><a href="http://www.akitaonrails.com/2015/12/02/ex-manga-downloadr-parte-3-suporte-ao-mangafox"target="_blank" rel="noopener">Ontem adicionei suporte ao Mangafox</a> na minha ferramenta de download e isso também acabou jogando um pouco de código sujo no meu código que já não estava lá essas coisas. Hora de uma faxina pra valer.</p>
<p>Você consegue ver tudo o que fiz desde ontem pra limpar a casa através da <a href="https://github.com/akitaonrails/ex_manga_downloadr/compare/59694565592f8a3bea95115b858dd2ddfdc89873...3ae7dd98a8fd7bae47ffd7a24c0c42a2c3777fad"target="_blank" rel="noopener">excelente página de compare do Github</a></p>
<p>Antes de mais nada: agora a escolha de ter adicionado uma quantidade razoável de testes vai pagar dividendos. Neste refactoring eu mudei assinaturas de funções, formatos de resposta, movi uma boa quantidade de código de lugar, e sem os testes essa empreitada teria me tomado o dia inteiro ou mais, deixando o esforço de refactor questionável desde o início.</p>
<p>A cada passo do refactoring eu podia rodar &ldquo;<tt>mix test</tt>&rdquo; e trabalhar até chegar no status verde:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Finished in 13.5 seconds (0.1s on load, 13.4s on tests)
12 tests, 0 failures</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>Os testes estão demorando bastante porque fiz a escolha de que os testes unitários do MangaReader e do Mangafox fossem de fato online, buscando direto dos sites. Demora mais pra rodar a suíte mas eu sei que se ela quebrar e eu não tiver mexido naquele código, então os sites de origem mudaram seus formatos e eu preciso mudar o parser. Eu poderia ter adicionado fixtures pra fazer os testes rodarem mais rápido, mas o objetivo do meu parser é estar correto.</p>
<h3>Macros pro Resgate!<span class="hx:absolute hx:-mt-20" id="macros-pro-resgate"></span>
    <a href="#macros-pro-resgate" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Cada módulo de fonte tem 3 sub-módulos: ChapterPage, IndexPage e Page. Todos eles tem uma função principal que parece com este pedaço de 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="n">defmodule</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">Mangafox</span><span class="o">.</span><span class="n">ChapterPage</span> <span class="k">do</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 class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">pages</span><span class="p">(</span><span class="n">chapter_link</span><span class="p">)</span> <span class="k">do</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;Fetching pages from chapter </span><span class="si">#{</span><span class="n">chapter_link</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">case</span> <span class="no">HTTPotion</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">chapter_link</span><span class="p">,</span> <span class="o">[</span><span class="ss">headers</span><span class="p">:</span> <span class="o">[</span><span class="s2">&#34;User-Agent&#34;</span><span class="p">:</span> <span class="vi">@user_agent</span><span class="p">,</span> <span class="s2">&#34;Accept-encoding&#34;</span><span class="p">:</span> <span class="s2">&#34;gzip&#34;</span><span class="o">]</span><span class="p">,</span> <span class="ss">timeout</span><span class="p">:</span> <span class="mi">30_000</span><span class="o">]</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="o">%</span><span class="no">HTTPotion</span><span class="o">.</span><span class="n">Response</span><span class="p">{</span> <span class="ss">body</span><span class="p">:</span> <span class="n">body</span><span class="p">,</span> <span class="ss">headers</span><span class="p">:</span> <span class="n">headers</span><span class="p">,</span> <span class="ss">status_code</span><span class="p">:</span> <span class="mi">200</span> <span class="p">}</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="n">body</span> <span class="o">=</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">Mangafox</span><span class="o">.</span><span class="n">gunzip</span><span class="p">(</span><span class="n">body</span><span class="p">,</span> <span class="n">headers</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="n">fetch_pages</span><span class="p">(</span><span class="n">chapter_link</span><span class="p">,</span> <span class="n">body</span><span class="p">)</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="p">{</span> <span class="ss">:err</span><span class="p">,</span> <span class="s2">&#34;not found&#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><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>(Listing 1.1)</p>
<p>Ele chama &ldquo;<tt>HTTPotion.get/2</tt>&rdquo; mandando um monte de opções HTTP e recebe uma struct &ldquo;<tt>%HTTPotion.Response</tt>&rdquo; que então é decomposta pra pegar o body e os headers. Aplica gunzip no body se necessário e vai parsear o HTML em si.</p>
<p>Código parecido existe em 6 módulos diferentes, com links diferentes e funções de parser diferentes. É muita repetição, mas e se a gente fizesse o código acima ficar parecido com o snippet abaixo?</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">ExMangaDownloadr</span><span class="o">.</span><span class="n">Mangafox</span><span class="o">.</span><span class="n">ChapterPage</span> <span class="k">do</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 class="nb">require</span> <span class="no">ExMangaDownloadr</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">pages</span><span class="p">(</span><span class="n">chapter_link</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">fetch</span> <span class="n">chapter_link</span><span class="p">,</span> <span class="k">do</span><span class="p">:</span> <span class="n">fetch_pages</span><span class="p">(</span><span class="n">chapter_link</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>(Listing 1.2)</p>
<p>Mudei 9 linhas pra apenas 1. E aliás, essa mesma linha pode ser escrita 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="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">fetch</span> <span class="n">chapter_link</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">fetch_pages</span><span class="p">(</span><span class="n">chapter_link</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>Parece familiar? É como todo bloco na linguagem Elixir, você pode escrevê-lo no formato de bloco &ldquo;do/end&rdquo; ou da maneira que ele realmente é por baixo dos panos: uma keyword list com uma chave chamada &ldquo;:do&rdquo;. E a forma como esse macro é definido é 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">ExMangaDownloadr</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="n">defmacro</span> <span class="n">fetch</span><span class="p">(</span><span class="n">link</span><span class="p">,</span> <span class="k">do</span><span class="p">:</span> <span class="n">expression</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">quote</span> <span class="k">do</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;Fetching from </span><span class="si">#{</span><span class="n">unquote</span><span class="p">(</span><span class="n">link</span><span class="p">)</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">case</span> <span class="no">HTTPotion</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">unquote</span><span class="p">(</span><span class="n">link</span><span class="p">),</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">http_headers</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">        <span class="o">%</span><span class="no">HTTPotion</span><span class="o">.</span><span class="n">Response</span><span class="p">{</span> <span class="ss">body</span><span class="p">:</span> <span class="n">body</span><span class="p">,</span> <span class="ss">headers</span><span class="p">:</span> <span class="n">headers</span><span class="p">,</span> <span class="ss">status_code</span><span class="p">:</span> <span class="mi">200</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">:ok</span><span class="p">,</span> <span class="n">body</span> <span class="o">|&gt;</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">gunzip</span><span class="p">(</span><span class="n">headers</span><span class="p">)</span> <span class="o">|&gt;</span> <span class="n">unquote</span><span class="p">(</span><span class="n">expression</span><span class="p">)</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="p">{</span> <span class="ss">:err</span><span class="p">,</span> <span class="s2">&#34;not found&#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><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>(Listing 1.3)</p>
<p>Tem um monte de detalhes pra considerar quando se escreve um macro e eu recomendo ler a documentação sobre <a href="http://elixir-lang.org/getting-started/meta/macros.html"target="_blank" rel="noopener">Macros</a>. O código está basicamente copiando o corpo da função do &ldquo;<tt>ChapterPage.pages/1</tt>&rdquo; (Listing 1.1) e colando dentro do bloco &ldquo;<tt>quote do .. end</tt>&rdquo; (Listing 1.3).</p>
<p>Dentro daquele código a gente tem &ldquo;<tt>unquote(link)</tt>&rdquo; e &ldquo;<tt>unquote(expression)</tt>&rdquo;. Você também precisa ler a documentação sobre <a href="http://elixir-lang.org/getting-started/meta/quote-and-unquote.html"target="_blank" rel="noopener">&ldquo;Quote and Unquote&rdquo;</a>. Ele simplesmente expande esse código &ldquo;externo&rdquo; dentro do código do macro pra adiar a execução até o código do quote do macro de fato ser executado, em vez de rodar naquele exato momento. Eu sei, complicado de embrulhar a cabeça em volta disso na primeira vez.</p>
<p>A linha de fundo é: qualquer código que estiver dentro do bloco &ldquo;quote&rdquo; vai ser &ldquo;inserido&rdquo; onde a gente chamou &ldquo;<tt>ExMangaDownloadr.fetch/2</tt>&rdquo; na função &ldquo;pages/1&rdquo; da Listing 1.2, junto com o código unquoted que você passou como parâmetro.</p>
<p>O código resultante vai parecer com o código original da Listing 1.1.</p>
<p>Pra simplificar, se você estivesse em Javascript isso seria um código parecido:</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">fetch</span><span class="p">(</span><span class="nx">url</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nb">eval</span><span class="p">(</span><span class="s2">&#34;doSomething(&#39;&#34;</span> <span class="o">+</span> <span class="nx">url</span> <span class="o">+</span> <span class="s2">&#34;&#39;)&#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 class="kd">function</span> <span class="nx">pages</span><span class="p">(</span><span class="nx">page_link</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">fetch</span><span class="p">(</span><span class="nx">page_link</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>&ldquo;Quote&rdquo; seria como o corpo de string num eval e &ldquo;unquote&rdquo; só concatenando o valor que você passou dentro do código que está sendo eval-ado. É uma metáfora grosseira porque &ldquo;quote/unquote&rdquo; é <strong>muito</strong> mais poderoso e mais limpo que o feio &ldquo;eval&rdquo; (que você não deveria estar usando, aliás!) Mas essa metáfora serve pra você entender o código acima.</p>
<p>Outro lugar onde usei um macro foi pra salvar a lista de imagens num arquivo de dump e carregá-la depois caso a ferramenta dê crash por algum motivo, pra não ter que começar tudo do zero. O código original era 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">dump_file</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="si">#{</span><span class="n">directory</span><span class="si">}</span><span class="s2">/images_list.dump&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">images_list</span> <span class="o">=</span> <span class="k">if</span> <span class="no">File</span><span class="o">.</span><span class="n">exists?</span><span class="p">(</span><span class="n">dump_file</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">                <span class="ss">:erlang</span><span class="o">.</span><span class="n">binary_to_term</span><span class="p">(</span><span class="no">File</span><span class="o">.</span><span class="n">read!</span><span class="p">(</span><span class="n">dump_file</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">list</span> <span class="o">=</span> <span class="o">[</span><span class="n">url</span><span class="p">,</span> <span class="n">source</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">                  <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">chapters</span>
</span></span><span class="line"><span class="cl">                  <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">pages</span>
</span></span><span class="line"><span class="cl">                  <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">images_sources</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="p">(</span><span class="n">dump_file</span><span class="p">,</span> <span class="ss">:erlang</span><span class="o">.</span><span class="n">term_to_binary</span><span class="p">(</span><span class="n">list</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">                <span class="n">list</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>(Listing 1.4)</p>
<p>E agora que você entende macros, vai entender o que eu fiz aqui:</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">ExMangaDownloadr</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="n">defmacro</span> <span class="n">managed_dump</span><span class="p">(</span><span class="n">directory</span><span class="p">,</span> <span class="k">do</span><span class="p">:</span> <span class="n">expression</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">quote</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="n">dump_file</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="si">#{</span><span class="n">unquote</span><span class="p">(</span><span class="n">directory</span><span class="p">)</span><span class="si">}</span><span class="s2">/images_list.dump&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="n">images_list</span> <span class="o">=</span> <span class="k">if</span> <span class="no">File</span><span class="o">.</span><span class="n">exists?</span><span class="p">(</span><span class="n">dump_file</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">          <span class="ss">:erlang</span><span class="o">.</span><span class="n">binary_to_term</span><span class="p">(</span><span class="no">File</span><span class="o">.</span><span class="n">read!</span><span class="p">(</span><span class="n">dump_file</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">list</span> <span class="o">=</span> <span class="n">unquote</span><span class="p">(</span><span class="n">expression</span><span class="p">)</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="p">(</span><span class="n">dump_file</span><span class="p">,</span> <span class="ss">:erlang</span><span class="o">.</span><span class="n">term_to_binary</span><span class="p">(</span><span class="n">list</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">          <span class="n">list</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="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">defmodule</span> <span class="no">ExMangaDownloadr</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">ExMangaDownloadr</span><span class="o">.</span><span class="n">Workflow</span>
</span></span><span class="line"><span class="cl">  <span class="nb">require</span> <span class="no">ExMangaDownloadr</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">process</span><span class="p">(</span><span class="n">manga_name</span><span class="p">,</span> <span class="n">directory</span><span class="p">,</span> <span class="p">{</span><span class="n">_url</span><span class="p">,</span> <span class="n">_source</span><span class="p">}</span> <span class="o">=</span> <span class="n">manga_site</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="no">File</span><span class="o">.</span><span class="n">mkdir_p!</span><span class="p">(</span><span class="n">directory</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">images_list</span> <span class="o">=</span> 
</span></span><span class="line"><span class="cl">      <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">managed_dump</span> <span class="n">directory</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">        <span class="n">manga_site</span>
</span></span><span class="line"><span class="cl">          <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">chapters</span>
</span></span><span class="line"><span class="cl">          <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">pages</span>
</span></span><span class="line"><span class="cl">          <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">images_sources</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><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 pronto! E agora você vê como blocos &ldquo;do .. end&rdquo; são implementados. Ele simplesmente passa a expressão como o valor na keyword list da definição do macro. Vamos definir um macro bobo:</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">Foo</span>
</span></span><span class="line"><span class="cl">  <span class="n">defmacro</span> <span class="n">foo</span><span class="p">(</span><span class="k">do</span><span class="p">:</span> <span class="n">expression</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">     <span class="n">quote</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">       <span class="n">unquote</span><span class="p">(</span><span class="n">expression</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>
<p>E agora as seguintes chamadas são todas equivalentes:</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="no">Foo</span>
</span></span><span class="line"><span class="cl"><span class="no">Foo</span><span class="o">.</span><span class="n">foo</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="p">(</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="no">Foo</span><span class="o">.</span><span class="n">foo</span> <span class="k">do</span><span class="p">:</span> <span class="no">IO</span><span class="o">.</span><span class="n">puts</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="no">Foo</span><span class="o">.</span><span class="n">foo</span><span class="p">(</span><span class="k">do</span><span class="p">:</span> <span class="no">IO</span><span class="o">.</span><span class="n">puts</span><span class="p">(</span><span class="mi">1</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="no">Foo</span><span class="o">.</span><span class="n">foo</span><span class="p">(</span><span class="o">[</span><span class="k">do</span><span class="p">:</span> <span class="no">IO</span><span class="o">.</span><span class="n">puts</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">]</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="no">Foo</span><span class="o">.</span><span class="n">foo</span><span class="p">(</span><span class="o">[</span><span class="p">{</span><span class="ss">:do</span><span class="p">,</span> <span class="no">IO</span><span class="o">.</span><span class="n">puts</span><span class="p">(</span><span class="mi">1</span><span class="p">)}</span><span class="o">]</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>Isso é macros combinados com <a href="http://elixir-lang.org/getting-started/maps-and-dicts.html"target="_blank" rel="noopener">Keyword Lists</a> que eu expliquei em artigos anteriores e é simplesmente uma List com tuplas onde cada tupla tem uma chave atom e um valor.</p>
<h3>Mais Macros<span class="hx:absolute hx:-mt-20" id="mais-macros"></span>
    <a href="#mais-macros" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Outra oportunidade pra refatorar foram os módulos &ldquo;mangareader.ex&rdquo; e &ldquo;mangafox.ex&rdquo; que eram só usados nos testes unitários &ldquo;mangareader_test.ex&rdquo; e &ldquo;mangafox_test.ex&rdquo;. Esse é o código antigo do &ldquo;mangareader.ex&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">ExMangaDownloadr</span><span class="o">.</span><span class="n">MangaReader</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">defmacro</span> <span class="n">__using__</span><span class="p">(</span><span class="n">_opts</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">quote</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="k">alias</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">MangaReader</span><span class="o">.</span><span class="n">IndexPage</span>
</span></span><span class="line"><span class="cl">      <span class="k">alias</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">MangaReader</span><span class="o">.</span><span class="n">ChapterPage</span>
</span></span><span class="line"><span class="cl">      <span class="k">alias</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">MangaReader</span><span class="o">.</span><span class="n">Page</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>E é assim que ele era usado no &ldquo;mangareader_test.ex&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">ExMangaDownloadr</span><span class="o">.</span><span class="n">MangaReaderTest</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">ExUnit</span><span class="o">.</span><span class="n">Case</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 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>Era só um atalho pra fazer o alias dos módulos pra usá-los diretamente dentro dos testes. Eu simplesmente movi o módulo inteiro como um macro no módulo &ldquo;ex_manga_downloadr.ex&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">mangareader</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">quote</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="k">alias</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">MangaReader</span><span class="o">.</span><span class="n">IndexPage</span>
</span></span><span class="line"><span class="cl">      <span class="k">alias</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">MangaReader</span><span class="o">.</span><span class="n">ChapterPage</span>
</span></span><span class="line"><span class="cl">      <span class="k">alias</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">MangaReader</span><span class="o">.</span><span class="n">Page</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">mangafox</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="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">defmacro</span> <span class="n">__using__</span><span class="p">(</span><span class="n">which</span><span class="p">)</span> <span class="k">when</span> <span class="n">is_atom</span><span class="p">(</span><span class="n">which</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">apply</span><span class="p">(</span><span class="n">__MODULE__</span><span class="p">,</span> <span class="n">which</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 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 agora posso usá-lo assim no arquivo de teste:</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">ExMangaDownloadr</span><span class="o">.</span><span class="n">MangaReaderTest</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">ExUnit</span><span class="o">.</span><span class="n">Case</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">ExMangaDownloadr</span><span class="p">,</span> <span class="ss">:mangareader</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>O macro especial &ldquo;<strong>using</strong>&rdquo; é chamado quando eu dou &ldquo;use&rdquo; num módulo, e eu posso até passar argumentos pra ele. A implementação então usa &ldquo;apply/3&rdquo; pra chamar dinamicamente o macro correto. É exatamente assim que o Phoenix importa os behaviors apropriados pra Models, Views, Controllers, Router, por exemplo:</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">Pxblog</span><span class="o">.</span><span class="n">PageController</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">Pxblog</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 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>Os macros estão abertos num arquivo Phoenix e disponíveis no módulo &ldquo;web/web.ex&rdquo;, então só copiei o mesmo comportamento. E agora tenho 2 arquivos a menos pra me preocupar.</p>
<h3>Pequenos refactorings<span class="hx:absolute hx:-mt-20" id="pequenos-refactorings"></span>
    <a href="#pequenos-refactorings" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>No código anterior eu usava o &ldquo;<tt>String.to_atom/1</tt>&rdquo; pra converter a string do nome do módulo num atom, pra ser usado depois em chamadas &ldquo;apply/3&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">defp</span> <span class="n">manga_source</span><span class="p">(</span><span class="n">source</span><span class="p">,</span> <span class="n">module</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="n">source</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;mangareader&#34;</span> <span class="o">-&gt;</span> <span class="nb">String</span><span class="o">.</span><span class="n">to_atom</span><span class="p">(</span><span class="s2">&#34;Elixir.ExMangaDownloadr.MangaReader.</span><span class="si">#{</span><span class="n">module</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;mangafox&#34;</span>    <span class="o">-&gt;</span> <span class="nb">String</span><span class="o">.</span><span class="n">to_atom</span><span class="p">(</span><span class="s2">&#34;Elixir.ExMangaDownloadr.Mangafox.</span><span class="si">#{</span><span class="n">module</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>Mudei pra isto:</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="s2">&#34;mangareader&#34;</span> <span class="o">-&gt;</span> <span class="ss">:&#34;Elixir.ExMangaDownloadr.MangaReader.</span><span class="si">#{</span><span class="n">module</span><span class="si">}</span><span class="ss">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;mangafox&#34;</span>    <span class="o">-&gt;</span> <span class="ss">:&#34;Elixir.ExMangaDownloadr.Mangafox.</span><span class="si">#{</span><span class="n">module</span><span class="si">}</span><span class="ss">&#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>É só um atalho pra fazer a mesma coisa.</p>
<p>No parser eu também estava usando o <a href="https://github.com/philss/floki"target="_blank" rel="noopener">Floki</a> de forma errada. Então dá uma olhada nesse pedaço de código antigo:</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">fetch_manga_title</span><span class="p">(</span><span class="n">html</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="no">Floki</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="n">html</span><span class="p">,</span> <span class="s2">&#34;#mangaproperties h1&#34;</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="s2">&#34;h1&#34;</span><span class="p">,</span> <span class="o">[]</span><span class="p">,</span> <span class="o">[</span><span class="n">title</span><span class="o">]</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="n">title</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">at</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="n">defp</span> <span class="n">fetch_chapters</span><span class="p">(</span><span class="n">html</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="no">Floki</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="n">html</span><span class="p">,</span> <span class="s2">&#34;#listing a&#34;</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="n">fn</span> <span class="p">{</span><span class="s2">&#34;a&#34;</span><span class="p">,</span> <span class="o">[</span><span class="p">{</span><span class="s2">&#34;href&#34;</span><span class="p">,</span> <span class="n">url</span><span class="p">}</span><span class="o">]</span><span class="p">,</span> <span class="n">_</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="n">url</span> <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>Agora usando as funções helper melhores que o Floki provê:</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">fetch_manga_title</span><span class="p">(</span><span class="n">html</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">html</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="no">Floki</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="s2">&#34;#mangaproperties h1&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="no">Floki</span><span class="o">.</span><span class="n">text</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">defp</span> <span class="n">fetch_chapters</span><span class="p">(</span><span class="n">html</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">html</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="no">Floki</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="s2">&#34;#listing a&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="no">Floki</span><span class="o">.</span><span class="n">attribute</span><span class="p">(</span><span class="s2">&#34;href&#34;</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>Esse foi um caso de não ter lido a documentação como eu deveria. Bem mais limpo!</p>
<p>Fiz outras pequenas limpezas mas acho que isso cobre as mudanças principais. E finalmente, subi a versão pra &ldquo;1.0.0&rdquo; 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-ruby" data-lang="ruby"><span class="line"><span class="cl">   <span class="k">def</span> <span class="nf">project</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">     <span class="o">[</span><span class="ss">app</span><span class="p">:</span> <span class="ss">:ex_manga_downloadr</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="o">-</span>     <span class="ss">version</span><span class="p">:</span> <span class="s2">&#34;0.0.1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="o">+</span>     <span class="ss">version</span><span class="p">:</span> <span class="s2">&#34;1.0.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="ss">elixir</span><span class="p">:</span> <span class="s2">&#34;~&gt; 1.1&#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>E falando em versões, estou usando Elixir 1.1 mas preste atenção porque <a href="https://github.com/elixir-lang/elixir/blob/ef5ba3af059f76489631dc26b52ecaeff09af3fe/CHANGELOG.md"target="_blank" rel="noopener">Elixir 1.2</a> está logo aí na esquina e traz algumas gracinhas. Por exemplo, aquele macro que fazia o alias de alguns módulos poderia ser escrito assim agora:</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">mangareader</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">quote</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="k">alias</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">MangaReader</span><span class="o">.</span><span class="p">{</span><span class="no">IndexPage</span><span class="p">,</span> <span class="no">ChapterPage</span><span class="p">,</span> <span class="no">Page</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 essa é só 1 feature entre muitas outras melhorias de sintaxe e suporte ao mais novo <a href="http://www.erlang.org/news/88"target="_blank" rel="noopener">Erlang R18</a>. Fique de olho nos dois!</p>
]]></content:encoded><category>elixir-e-erlang</category><category>engenharia-de-software</category><category>testes</category></item><item><title>A Obrigatória "Flame War": Phoenix vs Node.js</title><link>https://akitaonrails.github.io/2015/12/03/a-obrigatoria-flame-war-phoenix-vs-nodejs/</link><guid isPermaLink="true">https://akitaonrails.github.io/2015/12/03/a-obrigatoria-flame-war-phoenix-vs-nodejs/</guid><pubDate>Thu, 03 Dec 2015 12:01:00 -0200</pubDate><description>&lt;p&gt;Já vou avisando logo de cara: este post vai ser muito injusto. Não só sou suspeito por não gostar de Javascript e Node.js, como neste momento estou animadíssimo e fascinado com Elixir e Erlang.&lt;/p&gt;
&lt;p&gt;Comparações são sempre injustas. Não existe isso de benchmark sintético &amp;ldquo;justo&amp;rdquo;, o autor está &lt;strong&gt;sempre&lt;/strong&gt; enviesado para chegar em algum resultado pré-determinado. É o velho caso da pseudociência onde você já tem a conclusão na cabeça e sai catando dados que confirmam essa conclusão. As variáveis são muitas demais. As pessoas acham que fica justo quando você roda na mesma máquina contra dois tipos &amp;ldquo;parecidos&amp;rdquo; de aplicação, mas não fica. E também não confie em mim, faça seus próprios testes.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Já vou avisando logo de cara: este post vai ser muito injusto. Não só sou suspeito por não gostar de Javascript e Node.js, como neste momento estou animadíssimo e fascinado com Elixir e Erlang.</p>
<p>Comparações são sempre injustas. Não existe isso de benchmark sintético &ldquo;justo&rdquo;, o autor está <strong>sempre</strong> enviesado para chegar em algum resultado pré-determinado. É o velho caso da pseudociência onde você já tem a conclusão na cabeça e sai catando dados que confirmam essa conclusão. As variáveis são muitas demais. As pessoas acham que fica justo quando você roda na mesma máquina contra dois tipos &ldquo;parecidos&rdquo; de aplicação, mas não fica. E também não confie em mim, faça seus próprios testes.</p>
<p>Dito tudo isso, vamos nos divertir um pouco, certo?</p>
<h3>O Obrigatório Aquecimento &ldquo;Hello World&rdquo;<span class="hx:absolute hx:-mt-20" id="o-obrigatório-aquecimento-hello-world"></span>
    <a href="#o-obrigat%c3%b3rio-aquecimento-hello-world" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Para este post bem curto, criei só um &ldquo;hello world&rdquo; com Node.js + Express, vou apontar para o endpoint raiz, o Express renderizando um template HTML supersimples com um título e um parágrafo.</p>
<p>Para Elixir, fiz o bootstrap de um projeto Phoenix básico e adicionei um endpoint extra chamado &ldquo;/teste&rdquo; no Router. Esse endpoint chama o PageController, depois a função &ldquo;teste&rdquo;, e renderiza um template EEX com o mesmo título e parágrafo do exemplo em Express.</p>
<p>Bem simples. O Phoenix faz mais coisa que o Express, mas isso aqui de qualquer forma não é para ser um julgamento justo. Escolhi o <a href="https://www.joedog.org/siege-home/"target="_blank" rel="noopener">Siege</a> como ferramenta de teste sem nenhum motivo especial. Você pode escolher a ferramenta que mais gostar. Estou rodando isso no meu Macbook Pro 2013 com 8 cores e 16GB de RAM, então esse benchmark nunca vai estourar a minha máquina.</p>
<p>O primeiro teste é uma rodada simples de 200 conexões concorrentes (o número de CPUs que tenho) disparando 8 requisições cada, totalizando 1.600. Primeiro, os resultados do Node.js + Express:</p>
<video controls>
<source src="https://s3.us-east-2.amazonaws.com/blip.tv/Elixir/Node+8+x+200.mp4">
Your browser does not support the video tag. [Direct Link](https://s3.us-east-2.amazonaws.com/blip.tv/Elixir/Node+8+x+200.mp4)
</source></video>
<p>A primeira rodada já quebrou algumas conexões, mas a segunda rodada se recuperou e terminou as 1.600 requisições. E estes são os resultados do Phoenix:</p>
<video controls>
<source src="https://s3.us-east-2.amazonaws.com/blip.tv/Elixir/Phoenix+8+x+200.mp4">
Your browser does not support the video tag. [Direct Link](https://s3.us-east-2.amazonaws.com/blip.tv/Elixir/Phoenix+8+x+200.mp4)
</source></video>
<p>Como dá para ver, o Node.js leva vantagem em tempo total gasto. Um único processo Node.js só consegue rodar uma única thread real do SO. Por isso ele teve que usar apenas um core de CPU (apesar de eu ter outros 7 cores sobrando). Por outro lado, o Elixir consegue alcançar todos os 8 cores da minha máquina, cada um rodando um Scheduler em uma thread real do SO. Então, se esse teste fosse CPU bound, deveria ter rodado 8 vezes mais rápido que a versão Node.js. Como o teste é majoritariamente um caso de operações I/O bound, a construção async esperta do Node.js dá conta do recado.</p>
<p>Esse não é um teste impressionante nem de longe. Mas estamos só esquentando.</p>
<p>Ah, e aproveitando, repare como os logs do Phoenix mostram os tempos de processamento das requisições em MICROssegundos em vez de milissegundos!</p>
<h3>A Diversão de Verdade<span class="hx:absolute hx:-mt-20" id="a-diversão-de-verdade"></span>
    <a href="#a-divers%c3%a3o-de-verdade" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Agora vem a diversão de verdade. Nesta segunda rodada, adicionei uma chamada bloqueante de &ldquo;sleep&rdquo; nos dois projetos, então cada requisição vai dormir por um segundo inteiro, e isso não é absurdo. Muitos programadores escrevem código ruim que bloqueia por esse tempo, processam dados demais do banco, renderizam templates complexos demais, e por aí vai. Nunca confie que um programador vai fazer as melhores práticas o tempo todo.</p>
<p>Daí, disparo o Siege com dez conexões concorrentes e só uma requisição cada, para começar.</p>
<video controls>
<source src="https://s3.us-east-2.amazonaws.com/blip.tv/Elixir/Node+1+x+10+Sleep.mp4">
Your browser does not support the video tag. [Direct Link](https://s3.us-east-2.amazonaws.com/blip.tv/Elixir/Node+1+x+10+Sleep.mp4)
</source></video>
<p>É por isso que no meu artigo anterior &ldquo;Why Elixir?&rdquo; (<a href="http://www.akitaonrails.com/2015/12/01/o-obrigatorio-por-que-elixir-visao-pessoal%29"target="_blank" rel="noopener">http://www.akitaonrails.com/2015/12/01/o-obrigatorio-por-que-elixir-visao-pessoal)</a>, repeti várias vezes o quão <strong>&ldquo;rudimentar&rdquo;</strong> é uma solução baseada no padrão Reactor. É facílimo bloquear um event loop single-threaded.</p>
<p>Se você ainda não sabia, como o Node.js funciona? Em resumo, é um simples loop infinito. Quando uma função Javascript roda, ela bloqueia esse event loop. A função tem que explicitamente devolver o controle para o loop para que outra função tenha a chance de rodar. Chamadas de I/O demoram e ficam paradas esperando uma resposta, então ela pode devolver o controle e esperar um callback para continuar rodando, e é por isso que você acaba na temida <em>&ldquo;pirâmide do callback hell&rdquo;</em>.</p>
<p>Agora, com tudo o que expliquei nos artigos anteriores, você já deve imaginar como Elixir + Phoenix vai se sair:</p>
<video controls>
<source src="https://s3.us-east-2.amazonaws.com/blip.tv/Elixir/Phoenix+2+x+400+-+Sleep.mp4">
Your browser does not support the video tag. [Direct Link](https://s3.us-east-2.amazonaws.com/blip.tv/Elixir/Phoenix+2+x+400+-+Sleep.mp4)
</source></video>
<p>Como esperado, isso é um passeio no parque para o Phoenix. Ele não tem um loop single-thread rudimentar esperando que as funções em execução devolvam o controle por boa vontade. Os Schedulers conseguem suspender à força corrotinas/processos rodando se acharem que estão demorando demais (a contagem de 2.000 reductions e configurações de prioridade), então cada processo em execução tem uma fatia justa de recursos para rodar.</p>
<p>Por causa disso, posso ir aumentando o número de requisições e conexões concorrentes, e continua <strong>rápido</strong>.</p>
<p>No Node.js, se uma função demora 1 segundo para rodar, ela bloqueia o loop. Quando finalmente retorna, a próxima função de 1 segundo pode rodar. É por isso que se eu tenho dez requisições demorando 1 segundo cada para rodar, o processo inteiro vai linearmente levar 10 segundos!</p>
<p>O que obviamente não escala! Se você &ldquo;fizer do jeito certo&rdquo;, consegue escalar. Mas para que se dar ao trabalho?</p>
<h3>&ldquo;Node&rdquo; feito direito<span class="hx:absolute hx:-mt-20" id="node-feito-direito"></span>
    <a href="#node-feito-direito" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Como nota lateral, acho irônico que &ldquo;Node&rdquo; se chame &ldquo;Node&rdquo;. Eu suporia que conectar múltiplos Nodes que se comunicam entre si fosse fácil. E na prática, <a href="http://www.sitepoint.com/how-to-create-a-node-js-cluster-for-speeding-up-your-apps/"target="_blank" rel="noopener">não é</a>.</p>
<p>Se eu tivesse subido 5 processos Node, em vez de 10 segundos, tudo demoraria 2 segundos, já que cinco requisições bloqueariam os 5 processos Node por 1 segundo, e quando retornassem, as próximas cinco requisições bloqueariam de novo. Isso é parecido com o que precisamos fazer com Ruby ou Python, que têm os temidos Global Interpreter Locks (GIL) que, na realidade, só conseguem rodar uma computação bloqueante por vez. (Ruby com Eventmachine e Python com Tornado ou Twisted são parecidos com a implementação de event loop reactor do Node.js).</p>
<p>O Elixir consegue fazer muito melhor para coordenar de fato nodes diferentes, e são as bases de Erlang que permitem que sistemas altamente distribuídos como <a href="https://www.ejabberd.im/"target="_blank" rel="noopener">ejabberd</a> ou <a href="https://www.rabbitmq.com/"target="_blank" rel="noopener">RabbitMQ</a> façam o que fazem com toda eficiência.</p>
<p>Olha como é simples para um Elixir Node detectar a presença de outros nodes Elixir e fazer com que mandem e recebam mensagens entre si:</p>
<video controls>
<source src="https://s3.us-east-2.amazonaws.com/blip.tv/Elixir/7+-+Nodes.mp4">
Your browser does not support the video tag. [Direct Link](https://s3.us-east-2.amazonaws.com/blip.tv/Elixir/7+-+Nodes.mp4)
</source></video>
<p>Isso, é simples assim. A gente usa Remote Procedure Calls (RPC) há décadas; isso não é novidade. Erlang implementou isso há anos, e está embutido e disponível para uso fácil out-of-the-box.</p>
<p>Nos sites deles, o ejabberd se autointitula um &ldquo;Robust, Scalable, and Extensible XMPP Server&rdquo;, e o RabbitMQ se autointitula &ldquo;Robust messaging for applications&rdquo;. Agora sabemos que eles merecem os rótulos &ldquo;Robust&rdquo; e &ldquo;Scalable&rdquo;.</p>
<p>Então, estamos nos matando para fazer coisas que estão polidas e prontas há anos. O Elixir é a chave para destravar toda essa bondade do Erlang agora mesmo. Vamos só usar e parar de dar de ombros.</p>
]]></content:encoded><category>performance</category><category>elixir-e-erlang</category><category>javascript</category></item><item><title>Ex Manga Downloadr - Parte 3: Suporte ao Mangafox!</title><link>https://akitaonrails.github.io/2015/12/02/ex-manga-downloadr-parte-3-suporte-ao-mangafox/</link><guid isPermaLink="true">https://akitaonrails.github.io/2015/12/02/ex-manga-downloadr-parte-3-suporte-ao-mangafox/</guid><pubDate>Wed, 02 Dec 2015 16:27:00 -0200</pubDate><description>&lt;p&gt;Eu achava que a &lt;a href="http://www.akitaonrails.com/2015/11/19/ex-manga-downloadr-parte-2-poolboy-ao-resgate"target="_blank" rel="noopener"&gt;Parte 2&lt;/a&gt; seria meu último artigo sobre essa ferramentinha, mas no fim das contas é divertido demais para largar tão rápido. Como sempre, todo o código fonte está no meu &lt;a href="https://github.com/akitaonrails/ex_manga_downloadr"target="_blank" rel="noopener"&gt;repositório do Github&lt;/a&gt;. E o resumo do post é que agora dá pra fazer isso:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;pre&gt;&lt;code&gt;git pull
mix escript.build
./ex_manga_downloadr -n onepunch -u http://mangafox.me/manga/onepunch_man/ -d /tmp/onepunch -s mangafox&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;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"&gt;
&lt;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"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;E pronto: download direto do Mangafox embutido! \o/&lt;/p&gt;</description><content:encoded><![CDATA[<p>Eu achava que a <a href="http://www.akitaonrails.com/2015/11/19/ex-manga-downloadr-parte-2-poolboy-ao-resgate"target="_blank" rel="noopener">Parte 2</a> seria meu último artigo sobre essa ferramentinha, mas no fim das contas é divertido demais para largar tão rápido. Como sempre, todo o código fonte está no meu <a href="https://github.com/akitaonrails/ex_manga_downloadr"target="_blank" rel="noopener">repositório do Github</a>. E o resumo do post é que agora dá pra fazer isso:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>git pull
mix escript.build
./ex_manga_downloadr -n onepunch -u http://mangafox.me/manga/onepunch_man/ -d /tmp/onepunch -s mangafox</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 pronto: download direto do Mangafox embutido! \o/</p>
<p>Tudo começou quando eu quis baixar um mangá que não existe no MangaReader mas está disponível no Mangafox.</p>
<p>Então a empreitada inicial foi copiar os módulos do parser do MangaReader (IndexPage, ChapterPage e Page) e colar numa pasta específica &ldquo;lib/ex_manga_downloadr/mangafox&rdquo;. A mesma coisa na pasta de testes unitários. Só copiar e colar os arquivos e trocar o nome do módulo &ldquo;MangaReader&rdquo; por &ldquo;Mangafox&rdquo;.</p>
<p>Claro que os formatos de URL são diferentes, os seletores CSS do Floki são um pouquinho diferentes, então é isso que precisa mudar no parser. Por exemplo, é assim que eu faço o parsing dos links de capítulo da página principal no MangaReader:</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">fetch_chapters</span><span class="p">(</span><span class="n">html</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="no">Floki</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="n">html</span><span class="p">,</span> <span class="s2">&#34;#listing a&#34;</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="n">fn</span> <span class="p">{</span><span class="s2">&#34;a&#34;</span><span class="p">,</span> <span class="o">[</span><span class="p">{</span><span class="s2">&#34;href&#34;</span><span class="p">,</span> <span class="n">url</span><span class="p">}</span><span class="o">]</span><span class="p">,</span> <span class="n">_</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="n">url</span> <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 essa é a mesma coisa, só que para o Mangafox:</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">fetch_chapters</span><span class="p">(</span><span class="n">html</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">html</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="no">Floki</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="s2">&#34;.chlist a[class=&#39;tips&#39;]&#34;</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="n">fn</span> <span class="p">{</span><span class="s2">&#34;a&#34;</span><span class="p">,</span> <span class="o">[</span><span class="p">{</span><span class="s2">&#34;href&#34;</span><span class="p">,</span> <span class="n">url</span><span class="p">},</span> <span class="p">{</span><span class="s2">&#34;title&#34;</span><span class="p">,</span> <span class="n">_</span><span class="p">},</span> <span class="p">{</span><span class="s2">&#34;class&#34;</span><span class="p">,</span> <span class="s2">&#34;tips&#34;</span><span class="p">}</span><span class="o">]</span><span class="p">,</span> <span class="n">_</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="n">url</span> <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>Exatamente a mesma lógica, mas a estrutura de pattern matching muda porque os nodes do DOM HTML retornados são diferentes.</p>
<p>Outra diferença é que o MangaReader devolve tudo em texto puro por padrão, enquanto o Mangafox devolve tudo Gzipado independente de eu mandar o header HTTP &ldquo;Accept-Encoding&rdquo; (curiosamente, se eu tento várias vezes ele muda de comportamento e às vezes manda texto puro).</p>
<p>O que eu fiz de diferente foi checar se a estrutura <tt>%HTTPotion.Response{}</tt> retornada tinha o header &ldquo;Content-Encoding&rdquo; setado como &ldquo;gzip&rdquo; e, se sim, descomprimir usando o pacote &ldquo;zlib&rdquo; embutido do Erlang (sem precisar importar nada!):</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">gunzip</span><span class="p">(</span><span class="n">body</span><span class="p">,</span> <span class="n">headers</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">headers</span><span class="o">[</span><span class="ss">:&#34;Content-Encoding&#34;</span><span class="o">]</span> <span class="o">==</span> <span class="s2">&#34;gzip&#34;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="ss">:zlib</span><span class="o">.</span><span class="n">gunzip</span><span class="p">(</span><span class="n">body</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">body</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 teria preferido que o HTTPotion já fizesse isso pronto pra mim (#OpportunityToContribute!), mas foi fácil o suficiente.</p>
<p>Depois que os testes unitários começaram a passar corretamente, com o scrapper (requisições HTTPotion) e o parser (seletores Floki) ajustados, chegou a hora de fazer meu Worker reconhecer a existência desse novo conjunto de módulos.</p>
<p>O módulo Workflow só chama o Worker, que por sua vez faz o trabalho pesado de buscar páginas e baixar imagens. O Worker chamava o módulo MangaReader diretamente, 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">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 class="nb">require</span> <span class="no">Logger</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">chapter_page</span><span class="p">(</span><span class="n">chapter_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">:chapter_page</span><span class="p">,</span> <span class="n">chapter_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 class="o">...</span>
</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">:chapter_page</span><span class="p">,</span> <span class="n">chapter_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">ChapterPages</span><span class="o">.</span><span class="n">pages</span><span class="p">(</span><span class="n">chapter_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 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>Aquele &ldquo;<tt>use ExMangaDownloadr.MangaReader</tt>&rdquo; lá em cima é só uma macro que cria os aliases para os módulos correspondentes:</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">ExMangaDownloadr</span><span class="o">.</span><span class="n">MangaReader</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">defmacro</span> <span class="n">__using__</span><span class="p">(</span><span class="n">_opts</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">quote</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="k">alias</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">MangaReader</span><span class="o">.</span><span class="n">IndexPage</span>
</span></span><span class="line"><span class="cl">      <span class="k">alias</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">MangaReader</span><span class="o">.</span><span class="n">ChapterPage</span>
</span></span><span class="line"><span class="cl">      <span class="k">alias</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">MangaReader</span><span class="o">.</span><span class="n">Page</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>Então quando eu chamo &ldquo;<tt>ChapterPages.pages(chapter_link)</tt>&rdquo; é um atalho pra usar o nome qualificado completo do módulo, tipo: &ldquo;<tt>ExMangaDownloadr.MangaReader.ChapterPages.pages(chapter_link)</tt>&rdquo;.</p>
<p>Um namespace de módulo Elixir é só um Atom. Nomes de módulos aninhados têm o nome completo separado por pontos, prefixado com seu pai. Por exemplo:</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">Foo</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">defmodule</span> <span class="no">Bar</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">defmodule</span> <span class="no">Xyz</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">       <span class="k">def</span> <span class="nf">teste</span> <span class="k">do</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="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>Você pode chamar &ldquo;<tt>Foo.Bar.Xyz.teste()</tt>&rdquo; e pronto. Mas tem uma pequena pegadinha. O Elixir também prefixa transparentemente o nome completo do módulo com &ldquo;Elixir&rdquo;. Então na real, o nome completo do módulo é &ldquo;Elixir.Foo.Bar.Xyz&rdquo;, pra garantir que nenhum módulo Elixir conflite com algum módulo Erlang existente.</p>
<p>Isso é importante por causa dessa nova função que eu adicionei primeiro no módulo Worker:</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">manga_source</span><span class="p">(</span><span class="n">source</span><span class="p">,</span> <span class="n">module</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="n">source</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;mangareader&#34;</span> <span class="o">-&gt;</span> <span class="nb">String</span><span class="o">.</span><span class="n">to_atom</span><span class="p">(</span><span class="s2">&#34;Elixir.ExMangaDownloadr.MangaReader.</span><span class="si">#{</span><span class="n">module</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;mangafox&#34;</span>    <span class="o">-&gt;</span> <span class="nb">String</span><span class="o">.</span><span class="n">to_atom</span><span class="p">(</span><span class="s2">&#34;Elixir.ExMangaDownloadr.Mangafox.</span><span class="si">#{</span><span class="n">module</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 eu mapeio de &ldquo;mangafox&rdquo; pro novo namespace &ldquo;ExMangaDownloadr.Mangafox.&rdquo;. E por causa da natureza dinâmica e baseada em troca de mensagens do Elixir, eu consigo trocar esse 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">handle_call</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">_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">ChapterPages</span><span class="o">.</span><span class="n">pages</span><span class="p">(</span><span class="n">chapter_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></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 esse:</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">: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="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">links</span> <span class="o">=</span> <span class="n">source</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="n">manga_source</span><span class="p">(</span><span class="s2">&#34;ChapterPage&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="n">apply</span><span class="p">(</span><span class="ss">:pages</span><span class="p">,</span> <span class="o">[</span><span class="n">chapter_link</span><span class="o">]</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="n">links</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></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 eu posso escolher entre os módulos &ldquo;Elixir.ExMangaDownloadr.Mangafox.ChapterPage&rdquo; ou &ldquo;Elixir.ExMangaDownloadr.MangaReader.ChapterPage&rdquo;, chamar a função <tt>pages/1</tt> e mandar o mesmo argumento de antes. Só preciso garantir que consigo receber a string &ldquo;source&rdquo; pela linha de comando agora, então mudo o módulo CLI 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">parse_args</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">parse</span> <span class="o">=</span> <span class="no">OptionParser</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">args</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="ss">switches</span><span class="p">:</span> <span class="o">[</span><span class="nb">name</span><span class="p">:</span> <span class="ss">:string</span><span class="p">,</span> <span class="ss">url</span><span class="p">:</span> <span class="ss">:string</span><span class="p">,</span> <span class="ss">directory</span><span class="p">:</span> <span class="ss">:string</span><span class="p">,</span> <span class="ss">source</span><span class="p">:</span> <span class="ss">:string</span><span class="o">]</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="ss">aliases</span><span class="p">:</span> <span class="o">[</span><span class="ss">n</span><span class="p">:</span> <span class="ss">:name</span><span class="p">,</span> <span class="ss">u</span><span class="p">:</span> <span class="ss">:url</span><span class="p">,</span> <span class="ss">d</span><span class="p">:</span> <span class="ss">:directory</span><span class="p">,</span> <span class="ss">s</span><span class="p">:</span> <span class="ss">:source</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">case</span> <span class="n">parse</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="o">[</span><span class="nb">name</span><span class="p">:</span> <span class="n">manga_name</span><span class="p">,</span> <span class="ss">url</span><span class="p">:</span> <span class="n">url</span><span class="p">,</span> <span class="ss">directory</span><span class="p">:</span> <span class="n">directory</span><span class="p">,</span> <span class="ss">source</span><span class="p">:</span> <span class="n">source</span><span class="o">]</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="n">process</span><span class="p">(</span><span class="n">manga_name</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">directory</span><span class="p">,</span> <span class="n">source</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="o">[</span><span class="nb">name</span><span class="p">:</span> <span class="n">manga_name</span><span class="p">,</span> <span class="ss">directory</span><span class="p">:</span> <span class="n">directory</span><span class="o">]</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="n">process</span><span class="p">(</span><span class="n">manga_name</span><span class="p">,</span> <span class="n">directory</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <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">-&gt;</span> <span class="n">process</span><span class="p">(</span><span class="ss">:help</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>Comparado com a versão anterior eu só adicionei o argumento string &ldquo;<tt>:source</tt>&rdquo; no OptionParser e passei o valor capturado para <tt>process/4</tt>. Eu deveria adicionar alguma validação aqui para evitar strings diferentes de &ldquo;mangareader&rdquo; ou &ldquo;mangafox&rdquo;, mas vou deixar isso pra outro momento.</p>
<p>E no módulo Workflow, em vez de começar só com a URL do mangá, agora preciso começar com a URL e a fonte do mangá:</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 class="n">url</span><span class="p">,</span> <span class="n">source</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">chapters</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">pages</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">images_sources</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 que significa que cada uma dessas funções precisa não só retornar a nova lista de URLs como também passar a source adiante:</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">chapters</span><span class="p">(</span><span class="o">[</span><span class="n">url</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="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">_manga_title</span><span class="p">,</span> <span class="n">chapter_list</span><span class="p">}</span> <span class="o">=</span> <span class="n">source</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Worker</span><span class="o">.</span><span class="n">manga_source</span><span class="p">(</span><span class="s2">&#34;IndexPage&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="n">apply</span><span class="p">(</span><span class="ss">:chapters</span><span class="p">,</span> <span class="o">[</span><span class="n">url</span><span class="o">]</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="o">[</span><span class="n">chapter_list</span><span class="p">,</span> <span class="n">source</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>Essa era a única função do módulo Workflow hardcoded pro MangaReader, então eu também a deixei dinâmica usando a mesma função <tt>manga_source/2</tt> do Worker, e repare que o valor de retorno é &ldquo;<tt>[chapter_list, source]</tt>&rdquo; em vez de só &ldquo;<tt>chapter_list</tt>&rdquo;.</p>
<p>E agora eu finalmente posso testar com &ldquo;<tt>mix test</tt>&rdquo; e criar o novo binário executável de linha de comando com &ldquo;<tt>mix escript.build</tt>&rdquo; e rodar a nova versão assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>./ex_manga_downloadr -n onepunch -u http://mangafox.me/manga/onepunch_man/ -d /tmp/onepunch -s mangafox</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 site do Mangafox é bem instável com várias conexões concorrentes e dá timeout rapidinho às vezes, cuspindo erros feios assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>15:58:46.637 [error] Task #PID&lt;0.2367.0&gt; started from #PID&lt;0.124.0&gt; terminating
** (stop) exited in: GenServer.call(#PID&lt;0.90.0&gt;, {:page_download_image, {&#34;http://z.mfcdn.net/store/manga/11362/TBD-053.2/compressed/h006.jpg&#34;, &#34;Onepunch-Man 53.2: 53rd Punch [Fighting Spirit] (2) at MangaFox.me-h006.jpg&#34;}, &#34;/tmp/onepunch&#34;}, 1000000)
    ** (EXIT) an exception was raised:
        ** (HTTPotion.HTTPError) connection_closing
            (httpotion) lib/httpotion.ex:209: HTTPotion.handle_response/1</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 não descobri como fazer retry de requisições HTTPotion direito. Mas uma coisinha que eu fiz foi adicionar um check de disponibilidade no módulo Worker. Assim você pode simplesmente rodar o mesmo comando de novo e ele vai retomar baixando só os arquivos que faltam:</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">download_image</span><span class="p">({</span><span class="n">image_src</span><span class="p">,</span> <span class="n">image_filename</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="n">filename</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="si">#{</span><span class="n">directory</span><span class="si">}</span><span class="s2">/</span><span class="si">#{</span><span class="n">image_filename</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="no">File</span><span class="o">.</span><span class="n">exists?</span><span class="p">(</span><span class="n">filename</span><span class="p">)</span> <span class="k">do</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;Image </span><span class="si">#{</span><span class="n">filename</span><span class="si">}</span><span class="s2"> already downloaded, skipping.&#34;</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="n">image_src</span><span class="p">,</span> <span class="n">filename</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">Logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&#34;Downloading image </span><span class="si">#{</span><span class="n">image_src</span><span class="si">}</span><span class="s2"> to </span><span class="si">#{</span><span class="n">filename</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">case</span> <span class="no">HTTPotion</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">image_src</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="o">[</span><span class="ss">headers</span><span class="p">:</span> <span class="o">[</span><span class="s2">&#34;User-Agent&#34;</span><span class="p">:</span> <span class="vi">@user_agent</span><span class="o">]</span><span class="p">,</span> <span class="ss">timeout</span><span class="p">:</span> <span class="vi">@http_timeout</span><span class="o">]</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="o">%</span><span class="no">HTTPotion</span><span class="o">.</span><span class="n">Response</span><span class="p">{</span> <span class="ss">body</span><span class="p">:</span> <span class="n">body</span><span class="p">,</span> <span class="ss">headers</span><span class="p">:</span> <span class="n">_headers</span><span class="p">,</span> <span class="ss">status_code</span><span class="p">:</span> <span class="mi">200</span> <span class="p">}</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="p">(</span><span class="n">filename</span><span class="p">,</span> <span class="n">body</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="n">image_src</span><span class="p">,</span> <span class="n">filename</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="p">{</span><span class="ss">:err</span><span class="p">,</span> <span class="n">image_src</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>
<p>Isso pelo menos reduz retrabalho. Outra coisa que ainda estou trabalhando é nesse outro pedaço da função principal &ldquo;CLI.process&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">defp</span> <span class="n">process</span><span class="p">(</span><span class="n">manga_name</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">directory</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="no">File</span><span class="o">.</span><span class="n">mkdir_p!</span><span class="p">(</span><span class="n">directory</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">dump_file</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="si">#{</span><span class="n">directory</span><span class="si">}</span><span class="s2">/images_list.dump&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="n">images_list</span> <span class="o">=</span> <span class="k">if</span> <span class="no">File</span><span class="o">.</span><span class="n">exists?</span><span class="p">(</span><span class="n">dump_file</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">                  <span class="ss">:erlang</span><span class="o">.</span><span class="n">binary_to_term</span><span class="p">(</span><span class="no">File</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="n">dump_file</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">list</span> <span class="o">=</span> <span class="o">[</span><span class="n">url</span><span class="p">,</span> <span class="n">source</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">                    <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">chapters</span>
</span></span><span class="line"><span class="cl">                    <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">pages</span>
</span></span><span class="line"><span class="cl">                    <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">images_sources</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="p">(</span><span class="n">dump_file</span><span class="p">,</span> <span class="ss">:erlang</span><span class="o">.</span><span class="n">term_to_binary</span><span class="p">(</span><span class="n">list</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">                  <span class="n">list</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">images_list</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">process_downloads</span><span class="p">(</span><span class="n">directory</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">optimize_images</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">compile_pdfs</span><span class="p">(</span><span class="n">manga_name</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="n">finish_process</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 dá pra ver, a ideia é serializar os links finais das URLs das imagens num arquivo usando o serializador embutido &ldquo;<tt>:erlang.binary_to_term/1</tt>&rdquo; e checar se esse arquivo de dump existe, e desserializar com &ldquo;<tt>:erlang.term_to_binary/1</tt>&rdquo; antes de buscar todas as páginas de novo. Agora o processo pode retomar direto da função <tt>process_downloads/2</tt>.</p>
<p>O Mangafox é terrivelmente instável e eu vou precisar descobrir uma forma melhor de fazer retry de conexões que deram timeout, sem precisar quebrar e reiniciar manualmente da linha de comando. Ou é um site ruim ou um site esperto que derruba scrappers como o meu, embora eu chute que seja só infraestrutura ruim do lado deles.</p>
<p>Se eu reduzo de 50 processos para 5 no pool, parece que ele consegue lidar melhor (mas o processo fica mais lento, claro):</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">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">5</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></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ê ver erros de timeout, mude esse parâmetro. O MangaReader ainda aguenta 50 ou mais de concorrência.</p>
<p>E agora você sabe como adicionar suporte pra mais fontes de mangá. Fique à vontade pra mandar um Pull Request! :-)</p>
]]></content:encoded><category>elixir-e-erlang</category><category>anime-e-manga</category><category>automacao</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>Elixir 101 - Apresentando a Sintaxe</title><link>https://akitaonrails.github.io/2015/11/25/elixir-101-apresentando-a-sintaxe/</link><guid isPermaLink="true">https://akitaonrails.github.io/2015/11/25/elixir-101-apresentando-a-sintaxe/</guid><pubDate>Wed, 25 Nov 2015 17:25:00 -0200</pubDate><description>&lt;p&gt;Venho publicando vários artigos nas últimas semanas, dá uma olhada na &lt;a href="http://www.akitaonrails.com/elixir"target="_blank" rel="noopener"&gt;tag &amp;ldquo;Elixir&amp;rdquo;&lt;/a&gt; pra ler todos eles.&lt;/p&gt;
&lt;p&gt;Muitas séries de tutoriais começam apresentando uma linguagem nova pela sintaxe. Eu inverti a ordem. Elixir não é interessante por causa da sintaxe. Erlang já é interessante por si só, por ser uma plataforma muito madura, altamente confiável, altamente concorrente e distribuída. Mas a sintaxe não é pra qualquer um. Não que seja &amp;ldquo;feia&amp;rdquo;, ela é só diferente demais pra quem vem da escola C digerir com facilidade. Ela deriva de Prolog, e este é um pequeno exemplo de um exercício em Prolog:&lt;/p&gt;</description><content:encoded><![CDATA[<p>Venho publicando vários artigos nas últimas semanas, dá uma olhada na <a href="http://www.akitaonrails.com/elixir"target="_blank" rel="noopener">tag &ldquo;Elixir&rdquo;</a> pra ler todos eles.</p>
<p>Muitas séries de tutoriais começam apresentando uma linguagem nova pela sintaxe. Eu inverti a ordem. Elixir não é interessante por causa da sintaxe. Erlang já é interessante por si só, por ser uma plataforma muito madura, altamente confiável, altamente concorrente e distribuída. Mas a sintaxe não é pra qualquer um. Não que seja &ldquo;feia&rdquo;, ela é só diferente demais pra quem vem da escola C digerir com facilidade. Ela deriva de Prolog, e este é um pequeno exemplo de um exercício em Prolog:</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-prolog" data-lang="prolog"><span class="line"><span class="cl"><span class="c1">% P03 (*): Find the K&#39;th element of a list.</span>
</span></span><span class="line"><span class="cl"><span class="c1">%     The first element in the list is number 1.</span>
</span></span><span class="line"><span class="cl"><span class="c1">%     Example:</span>
</span></span><span class="line"><span class="cl"><span class="c1">%     ?- element_at(X,[a,b,c,d,e],3).</span>
</span></span><span class="line"><span class="cl"><span class="c1">%     X = c</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">% The first element in the list is number 1.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">% element_at(X,L,K) :- X is the K&#39;th element of the list L</span>
</span></span><span class="line"><span class="cl"><span class="c1">%    (element,list,integer) (?,?,+)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">% Note: nth1(?Index, ?List, ?Elem) is predefined</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">element_at</span><span class="p">(</span><span class="nv">X</span><span class="p">,[</span><span class="nv">X</span><span class="p">|</span><span class="k">_</span><span class="p">],</span><span class="mi">1</span><span class="p">).</span>
</span></span><span class="line"><span class="cl"><span class="nf">element_at</span><span class="p">(</span><span class="nv">X</span><span class="p">,[</span><span class="k">_</span><span class="p">|</span><span class="nv">L</span><span class="p">],</span><span class="nv">K</span><span class="p">)</span> <span class="p">:-</span> <span class="nv">K</span> <span class="o">&gt;</span> <span class="mi">1</span><span class="p">,</span> <span class="nv">K1</span> <span class="o">is</span> <span class="nv">K</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span> <span class="nf">element_at</span><span class="p">(</span><span class="nv">X</span><span class="p">,</span><span class="nv">L</span><span class="p">,</span><span class="nv">K1</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>Erlang tem uma sintaxe parecida, com a ideia de frases divididas por vírgulas e terminando com um ponto.</p>
<p>O José Valim foi muito esperto: pegou a melhor das plataformas maduras disponíveis e cobriu com uma camada de sintaxe moderna e bibliotecas padrão mais fáceis de usar. Esse é o mesmo problema implementado em Elixir:</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">Exercise</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">element_at</span><span class="p">(</span><span class="o">[</span><span class="n">found</span><span class="o">|</span><span class="n">_</span><span class="o">]</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="n">found</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">element_at</span><span class="p">(</span><span class="o">[</span><span class="n">_</span><span class="o">|</span><span class="n">rest</span><span class="o">]</span><span class="p">,</span> <span class="n">position</span><span class="p">)</span> <span class="k">when</span> <span class="n">position</span> <span class="o">&gt;</span> <span class="mi">1</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">element_at</span><span class="p">(</span><span class="n">rest</span><span class="p">,</span> <span class="n">position</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 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 eu copiar e colar o código acima num shell IEx, posso testar assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>iex(7)&gt; Exercise.element_at([&#34;a&#34;, &#34;b&#34;, &#34;c&#34;, &#34;d&#34;, &#34;e&#34;], 3)
&#34;c&#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 exercício simples já mostra alguns dos pontos poderosos de Erlang dos quais o Elixir tira proveito, como pattern matching e recursão.</p>
<p>Antes de mais nada, toda função <strong>precisa</strong> ser definida dentro de um módulo, que você nomeia com <tt>defmodule My.Module do .. end</tt>. Internamente, isso vira o atom &ldquo;Elixir.My.Process&rdquo;. Aninhar módulos é só um nome maior concatenado com pontos.</p>
<p>Daí você define funções públicas com o bloco <tt>def my_function(args) do .. end</tt>, que é só um macro pra mesma construção <tt>def my_function(args), do: &hellip;</tt>. Métodos privados são declarados com <tt>defp</tt>.</p>
<p>Uma função é, na verdade, identificada pelo par nome e aridade. Então acima temos <tt>element_at/2</tt>, o que significa que aceita 2 argumentos. Mas temos 2 funções com a mesma aridade: a diferença é o <strong>pattern matching</strong>.</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">element_at</span><span class="p">(</span><span class="o">[</span><span class="n">found</span><span class="o">|</span><span class="n">_</span><span class="o">]</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="n">found</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>Aqui estamos dizendo: o primeiro argumento será um array, decomponha-o. O primeiro elemento do array será armazenado na variável &ldquo;found&rdquo;, o resto &ldquo;_&rdquo; será ignorado. E o segundo argumento precisa ser o número &ldquo;1&rdquo;. Essa é a descrição do chamado &ldquo;padrão&rdquo; (pattern), que deve &ldquo;casar&rdquo; com os argumentos de entrada recebidos. Essa é a semântica do <strong>&ldquo;call-by-pattern&rdquo;</strong>.</p>
<p>Mas e se quisermos passar uma posição diferente de &ldquo;1&rdquo;? É pra isso que serve a segunda definição:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>def element_at([_|rest], position) when position &gt; 1 do</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 primeiro argumento de novo <strong>precisa</strong> ser um array, mas dessa vez não nos importamos com o primeiro elemento, só com o resto do array sem o primeiro elemento. E qualquer posição diferente de &ldquo;1&rdquo; será armazenada na variável &ldquo;position&rdquo;.</p>
<p>Mas essa função é especial, ela tem uma <strong>guarda</strong> que só permite uma <tt>position</tt> maior que 1. E se tentarmos uma posição negativa?</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>iex(8)&gt; Exercise.element_at([&#34;a&#34;, &#34;b&#34;, &#34;c&#34;, &#34;d&#34;, &#34;e&#34;], -3)
** (FunctionClauseError) no function clause matching in Exercise.element_at/2
    iex:7: Exercise.element_at([&#34;a&#34;, &#34;b&#34;, &#34;c&#34;, &#34;d&#34;, &#34;e&#34;], -3)</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>Ele diz que nenhuma das cláusulas que passamos casa com nenhuma das definidas acima. Poderíamos ter adicionado uma terceira definição justamente pra capturar esses casos:</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">element_at</span><span class="p">(</span><span class="n">_list</span><span class="p">,</span> <span class="n">_position</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="kp">nil</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>Colocar o underscore &ldquo;_&rdquo; antes do nome da variável é o mesmo que ter só o underscore, mas estamos nomeando pra ficar mais legível. Quaisquer argumentos passados serão simplesmente ignorados. E esse é o caso mais genérico, quando os 2 anteriores não casam.</p>
<p>A linha anterior é o mesmo que escrever:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>def element_at(_list, _position) do
  nil
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>Não vou entrar em macros agora, só saiba que existe mais de uma forma de fazer as coisas em Elixir e que você pode definir essas formas usando o suporte nativo do Erlang a macros, código dinâmico que é compilado em tempo de execução. É a maneira de fazer metaprogramação em Elixir.</p>
<p>Voltando à implementação, a primeira função ainda pode parecer estranha, vamos revisar:</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">element_at</span><span class="p">(</span><span class="o">[</span><span class="n">_</span><span class="o">|</span><span class="n">rest</span><span class="o">]</span><span class="p">,</span> <span class="n">position</span><span class="p">)</span> <span class="k">when</span> <span class="n">position</span> <span class="o">&gt;</span> <span class="mi">1</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">element_at</span><span class="p">(</span><span class="n">rest</span><span class="p">,</span> <span class="n">position</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></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 que acontece é: quando chamamos <tt>Exercise.element_at([&ldquo;a&rdquo;, &ldquo;b&rdquo;, &ldquo;c&rdquo;, &ldquo;d&rdquo;, &ldquo;e&rdquo;], 3)</tt>, o primeiro argumento casa com o padrão <tt>[_|rest]</tt>. O primeiro elemento &ldquo;a&rdquo; é descartado e a nova lista <tt>[&ldquo;b&rdquo;, &ldquo;c&rdquo;, &ldquo;d&rdquo;, &ldquo;e&rdquo;]</tt> é armazenada como &ldquo;<tt>rest</tt>&rdquo;.</p>
<p>Por fim, recursamos a chamada decrementando a variável &ldquo;<tt>position</tt>&rdquo;. Então vira <tt>element_at([&ldquo;b&rdquo;, &ldquo;c&rdquo;, &ldquo;d&rdquo;, &ldquo;e&rdquo;], 2)</tt>. E isso se repete até position virar &ldquo;1&rdquo;, quando o pattern matching cai na segunda função, definida assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>def element_at([found|_], 1), do: found</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 caso, o resto do array é casado por padrão e o primeiro elemento, &ldquo;c&rdquo;, é armazenado na variável &ldquo;<tt>found</tt>&rdquo;, o resto do array é descartado. Só chegou aqui porque a posição casou como &ldquo;1&rdquo;, e então a função simplesmente retorna a variável &ldquo;found&rdquo;, que contém o 3º elemento do array original, &ldquo;c&rdquo;.</p>
<p>Isso tudo é bonito e elegante, mas em Elixir podíamos só ter feito esta outra versã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">defmodule</span> <span class="no">Exercise</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">element_at</span><span class="p">(</span><span class="n">list</span><span class="p">,</span> <span class="n">position</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="no">Enum</span><span class="o">.</span><span class="n">at</span><span class="p">(</span><span class="n">list</span><span class="p">,</span> <span class="n">position</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>E pronto! Vários tutoriais vão falar como recursão e pattern matching pra decompor listas resolvem um monte de problemas, mas o Elixir nos dá a conveniência de tratar listas como Enumerables e oferece um módulo <a href="http://elixir-lang.org/docs/stable/elixir/Enum.html"target="_blank" rel="noopener">Enum</a> bem rico, com funções muito úteis como <tt>at/2</tt>, <tt>each/2</tt>, <tt>take/2</tt> e por aí vai. É só escolher o que precisa e você manipula listas como um chefe.</p>
<p>Ah, e por sinal, existe algo chamado <a href="http://elixir-lang.org/getting-started/sigils.html"target="_blank" rel="noopener">Sigil</a> no Elixir. Em vez de escrever a Lista de Strings explicitamente, podíamos ter feito assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>iex(8)&gt; ~w(a b c d e f)
[&#34;a&#34;, &#34;b&#34;, &#34;c&#34;, &#34;d&#34;, &#34;e&#34;, &#34;f&#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>Ou, se quiséssemos uma Lista de Atoms, podíamos fazer assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>iex(9)&gt; ~w(a b c d e f)a
[:a, :b, :c, :d, :e, :f]</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>Lists, Tuples e Keyword Lists<span class="hx:absolute hx:-mt-20" id="lists-tuples-e-keyword-lists"></span>
    <a href="#lists-tuples-e-keyword-lists" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Bom, isso foi simples demais. Você precisa mesmo ter a ideia de pattern matching e os tipos básicos na cabeça pra fluir. Vamos pegar outro trecho do <a href="http://www.akitaonrails.com/2015/11/18/ex-manga-downloadr-um-exercicio-com-elixir"target="_blank" rel="noopener">Ex Manga Downloadr</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="n">defp</span> <span class="n">parse_args</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">parse</span> <span class="o">=</span> <span class="no">OptionParser</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">args</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="ss">switches</span><span class="p">:</span> <span class="o">[</span><span class="nb">name</span><span class="p">:</span> <span class="ss">:string</span><span class="p">,</span> <span class="ss">url</span><span class="p">:</span> <span class="ss">:string</span><span class="p">,</span> <span class="ss">directory</span><span class="p">:</span> <span class="ss">:string</span><span class="o">]</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="ss">aliases</span><span class="p">:</span> <span class="o">[</span><span class="ss">n</span><span class="p">:</span> <span class="ss">:name</span><span class="p">,</span> <span class="ss">u</span><span class="p">:</span> <span class="ss">:url</span><span class="p">,</span> <span class="ss">d</span><span class="p">:</span> <span class="ss">:directory</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">case</span> <span class="n">parse</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="o">[</span><span class="nb">name</span><span class="p">:</span> <span class="n">manga_name</span><span class="p">,</span> <span class="ss">url</span><span class="p">:</span> <span class="n">url</span><span class="p">,</span> <span class="ss">directory</span><span class="p">:</span> <span class="n">directory</span><span class="o">]</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="n">process</span><span class="p">(</span><span class="n">manga_name</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">directory</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="o">[</span><span class="nb">name</span><span class="p">:</span> <span class="n">manga_name</span><span class="p">,</span> <span class="ss">directory</span><span class="p">:</span> <span class="n">directory</span><span class="o">]</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="n">process</span><span class="p">(</span><span class="n">manga_name</span><span class="p">,</span> <span class="n">directory</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <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">-&gt;</span> <span class="n">process</span><span class="p">(</span><span class="ss">:help</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>A primeira parte pode confundir você:</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">OptionParser</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">args</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="ss">switches</span><span class="p">:</span> <span class="o">[</span><span class="nb">name</span><span class="p">:</span> <span class="ss">:string</span><span class="p">,</span> <span class="ss">url</span><span class="p">:</span> <span class="ss">:string</span><span class="p">,</span> <span class="ss">directory</span><span class="p">:</span> <span class="ss">:string</span><span class="o">]</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="ss">aliases</span><span class="p">:</span> <span class="o">[</span><span class="ss">n</span><span class="p">:</span> <span class="ss">:name</span><span class="p">,</span> <span class="ss">u</span><span class="p">:</span> <span class="ss">:url</span><span class="p">,</span> <span class="ss">d</span><span class="p">:</span> <span class="ss">:directory</span><span class="o">]</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>O <tt>OptionParser.parse/2</tt> recebe só 2 argumentos: 2 arrays. Se você vem de Ruby, parece um Hash com colchetes opcionais, traduzindo pra algo parecido com isto:</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"># isto está errado</span>
</span></span><span class="line"><span class="cl"><span class="no">OptionParser</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">args</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="ss">switches</span><span class="p">:</span> <span class="p">{</span><span class="nb">name</span><span class="p">:</span> <span class="ss">:string</span><span class="p">,</span> <span class="ss">url</span><span class="p">:</span> <span class="ss">:string</span><span class="p">,</span> <span class="ss">directory</span><span class="p">:</span> <span class="ss">:string</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="ss">aliases</span><span class="p">:</span> <span class="p">{</span><span class="ss">n</span><span class="p">:</span> <span class="ss">:name</span><span class="p">,</span> <span class="ss">u</span><span class="p">:</span> <span class="ss">:url</span><span class="p">,</span> <span class="ss">d</span><span class="p">:</span> <span class="ss">:directory</span><span class="p">}</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>Isso funciona em Ruby, mas não é o caso em Elixir; existem colchetes opcionais, só que não onde você imagina:</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"># esta é a versão correta, mais explícita</span>
</span></span><span class="line"><span class="cl"><span class="no">OptionParser</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">args</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="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="ss">:switches</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="p">{</span><span class="ss">:name</span><span class="p">,</span> <span class="ss">:string</span><span class="p">},</span> <span class="p">{</span><span class="ss">:url</span><span class="p">,</span> <span class="ss">:string</span><span class="p">},</span> <span class="p">{</span><span class="ss">:directory</span><span class="p">,</span> <span class="ss">:string</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="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">aliases</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="p">{</span><span class="ss">:n</span><span class="p">,</span> <span class="ss">:name</span><span class="p">},</span> <span class="p">{</span><span class="ss">:u</span><span class="p">,</span> <span class="ss">:url</span><span class="p">},</span> <span class="p">{</span><span class="ss">:d</span><span class="p">,</span> <span class="ss">:directory</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="p">}</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></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>QUE!?!?</p>
<p>Isso mesmo, o segundo argumento é, na verdade, um array com elementos que são <strong>Tuples</strong>, pareados com chave atom e valor, e alguns dos valores são, eles próprios, arrays com tuples.</p>
<ol>
<li>
<p>em Elixir, Lists são o que normalmente chamamos de Array, uma Linked-List de elementos. Linked-Lists, como você sabe das aulas de Ciência da Computação, são fáceis pra inserir e remover elementos.</p>
</li>
<li>
<p>em Elixir, Tuples são listas fixas e imutáveis com posições fixas, com elementos delimitados pelas chaves &ldquo;{}&rdquo;</p>
</li>
</ol>
<p>Se o exemplo anterior foi demais, vamos dar um passo atrá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="n">defmodule</span> <span class="no">Teste</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">teste</span><span class="p">(</span><span class="n">opts</span><span class="p">)</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">:hello</span><span class="p">,</span> <span class="n">world</span><span class="p">},</span> <span class="p">{</span><span class="ss">:foo</span><span class="p">,</span> <span class="n">bar</span><span class="p">}</span><span class="o">]</span> <span class="o">=</span> <span class="n">opts</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="si">#{</span><span class="n">world</span><span class="si">}</span><span class="s2"> </span><span class="si">#{</span><span class="n">bar</span><span class="si">}</span><span class="s2">&#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></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 chamar assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>iex(13)&gt; Teste.teste hello: &#34;world&#34;, foo: &#34;bar&#34;
world bar</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>Que é o mesmo que chamar assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>iex(14)&gt; Teste.teste([{:hello, &#34;world&#34;}, {:foo, &#34;bar&#34;}])
world bar</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 pode confundir, mas é bem intuitivo. Você pode pensar nessa combinação de Lists (&quot;[]&quot;) com elementos Tuple contendo um par atom e valor (&quot;{:key, value}&quot;) como algo que se comporta quase como Hashes do Ruby usados pra argumentos nomeados opcionais.</p>
<p>Aí temos a parte de Pattern Match nos dois exemplos anteriores:</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">case</span> <span class="n">parse</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span><span class="o">[</span><span class="nb">name</span><span class="p">:</span> <span class="n">manga_name</span><span class="p">,</span> <span class="ss">url</span><span class="p">:</span> <span class="n">url</span><span class="p">,</span> <span class="ss">directory</span><span class="p">:</span> <span class="n">directory</span><span class="o">]</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">}</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="n">process</span><span class="p">(</span><span class="n">manga_name</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">directory</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span><span class="o">[</span><span class="nb">name</span><span class="p">:</span> <span class="n">manga_name</span><span class="p">,</span> <span class="ss">directory</span><span class="p">:</span> <span class="n">directory</span><span class="o">]</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">}</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="n">process</span><span class="p">(</span><span class="n">manga_name</span><span class="p">,</span> <span class="n">directory</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <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">-&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="n">process</span><span class="p">(</span><span class="ss">:help</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>E</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 class="p">{</span><span class="ss">:hello</span><span class="p">,</span> <span class="n">world</span><span class="p">},</span> <span class="p">{</span><span class="ss">:foo</span><span class="p">,</span> <span class="n">bar</span><span class="p">}</span><span class="o">]</span> <span class="o">=</span> <span class="n">opts</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 último exemplo é só decomposição. O exemplo anterior é pattern match e decomposição. Você casa com base nos atoms e nas posições dentro das tuples dentro da lista. Você casa do caso mais específico pro mais genérico. E, no processo, as variáveis do padrão ficam disponíveis pra usar dentro da cláusula correspondente do case.</p>
<p>Vamos entender o significado desta linha:</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="p">{</span><span class="o">[</span><span class="nb">name</span><span class="p">:</span> <span class="n">manga_name</span><span class="p">,</span> <span class="ss">url</span><span class="p">:</span> <span class="n">url</span><span class="p">,</span> <span class="ss">directory</span><span class="p">:</span> <span class="n">directory</span><span class="o">]</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="n">process</span><span class="p">(</span><span class="n">manga_name</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">directory</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>Ela está dizendo: dado o resultado da função <tt>OptionParser.parse/2</tt>, ele precisa ser uma tuple com 3 elementos. O segundo e o terceiro elementos não importam. Mas o primeiro elemento precisa ser uma List com pelo menos 3 tuples. E as chaves de cada tuple precisam ser os atoms <tt>:name</tt>, <tt>:url</tt> e <tt>:directory</tt>. Se estiverem lá, armazene os valores de cada tuple nas variáveis <tt>manga_name</tt>, <tt>url</tt> e <tt>directory</tt>, respectivamente.</p>
<p>Isso pode confundir bastante no início, mas essa combinação de uma List de Tuples é o que se chama <a href="http://elixir-lang.org/getting-started/maps-and-dicts.html#keyword-lists"target="_blank" rel="noopener"><strong>Keyword List</strong></a> e você vai encontrar esse padrão muitas vezes, então acostume-se.</p>
<p>Keyword List parece um Map, mas o Map tem uma sintaxe diferente:</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">list</span> <span class="o">=</span> <span class="o">[</span><span class="ss">a</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="ss">b</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="ss">c</span><span class="p">:</span> <span class="mi">3</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">map</span> <span class="o">=</span> <span class="sx">%{:a =&gt; 1, :b =&gt; 2, :c =&gt; 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>Isso deve resumir bem:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>iex(1)&gt; list = [a: 1, b: 2, c: 3]
[a: 1, b: 2, c: 3]
iex(2)&gt; map = %{:a =&gt; 1, :b =&gt; 2, :c =&gt; 3}
%{a: 1, b: 2, c: 3}

iex(3)&gt; list[:a]
1
iex(4)&gt; map[:a]
1

iex(5)&gt; list.a
** (ArgumentError) argument error
    :erlang.apply([a: 1, b: 2, c: 3], :a, [])
iex(5)&gt; map.a
1

iex(6)&gt; list2 = [{:a, 1}, {:b, 2}, {:c, 3}]
[a: 1, b: 2, c: 3]
iex(7)&gt; list = list2
[a: 1, b: 2, c: 3]</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>Keyword Lists são convenientes como argumentos de função ou valores de retorno. Mas se você quiser processar uma coleção de pares chave-valor, use uma estrutura tipo dicionário, neste caso, um Map. Especialmente se você precisar buscar na coleção pela chave. Eles parecem semelhantes, mas as estruturas internas são diferentes; uma Keyword List não é um Map, é só uma conveniência pra uma lista estática de tuples.</p>
<p>Por fim, se esse padrão casar com a variável <tt>parse</tt> passada no bloco <tt>case</tt>, ele executa <tt>process(manga_name, url, directory)</tt>, passando as 3 variáveis capturadas no match. Caso contrário, segue tentando o próximo padrão do bloco <tt>case</tt>.</p>
<p>A ideia é que o operador &ldquo;=&rdquo; não é uma &ldquo;atribuição&rdquo;, é um casador, você casa um lado com o outro. Leia a mensagem de erro quando um padrão não casa:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>iex(15)&gt; [a, b, c] = 1
** (MatchError) no match of right hand side value: 1</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 é um erro de matching, não um erro de atribuição. Mas se der certo, isso é o que temos:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>iex(15)&gt; [a, b, c] = [1, 2, 3]
[1, 2, 3]
iex(16)&gt; a
1
iex(17)&gt; c
3</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 é uma decomposição de List. Acontece que, no caso simples, parece uma atribuição de variável, mas é bem mais complexo do que isso.</p>
<h3>Pipelines<span class="hx:absolute hx:-mt-20" id="pipelines"></span>
    <a href="#pipelines" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Usamos exatamente esses conceitos de pattern matching nos elementos retornados do HTML parseado pelo Floki no meu Manga Downloadr:</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">Floki</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="n">html</span><span class="p">,</span> <span class="s2">&#34;#listing a&#34;</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="s2">&#34;a&#34;</span><span class="p">,</span> <span class="o">[</span><span class="p">{</span><span class="s2">&#34;href&#34;</span><span class="p">,</span> <span class="n">url</span><span class="p">}</span><span class="o">]</span><span class="p">,</span> <span class="n">_</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="n">url</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>O <tt>find/2</tt> pega uma string HTML da página baixada e casa com os seletores CSS do segundo argumento. O resultado é uma List de Tuples representando a estrutura de cada Nó HTML encontrado, neste caso, com este padrão: <tt>{&ldquo;a&rdquo;, [{&ldquo;href&rdquo;, url}], _}</tt></p>
<p>Daí podemos usar <tt>Enum.map/2</tt>. Um map é uma função que recebe cada elemento de uma lista e retorna uma nova lista com novos elementos. O primeiro argumento é a lista original e o segundo é uma função que recebe cada elemento e retorna um novo.</p>
<p>Uma das principais features da linguagem Elixir, que a maioria das linguagens não tem, é o operador <strong>Pipe</strong> (&quot;|&gt;&quot;). Ele se comporta quase como o operador pipe &ldquo;|&rdquo; do UNIX em qualquer shell.</p>
<p>No UNIX a gente costuma fazer coisas tipo &ldquo;<tt>ps -ef | grep PROCESS | grep -v grep | awk &lsquo;{print $2}&rsquo; | xargs kill -9</tt>&rdquo;</p>
<p>Isso é essencialmente o mesmo que fazer:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>ps -ef &gt; /tmp/ps.txt
grep mix /tmp/ps.txt &gt; /tmp/grep.txt
grep -v grep /tmp/grep.txt &gt; /tmp/grep2.txt
awk &#39;{print $2}&#39; /tmp/grep2.txt &gt; /tmp/awk.txt
xargs kill -9 &lt; /tmp/awk.txt</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>Cada processo UNIX pode receber algo da entrada padrão (STDIN) e enviar algo pra saída padrão (STDOUT). Podemos redirecionar a saída usando &ldquo;&gt;&rdquo;. Mas, em vez de fazer todos esses passos extras, criando um monte de arquivos temporários inúteis, podemos simplesmente &ldquo;pipear&rdquo; o STDOUT de um comando pro STDIN do próximo.</p>
<p>Elixir usa os mesmos princípios: podemos simplesmente usar o valor de retorno de uma função como o <strong>primeiro argumento</strong> da próxima função. Então o primeiro exemplo desta seção é o mesmo que fazer isto:</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">results</span> <span class="o">=</span> <span class="no">Floki</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="n">html</span><span class="p">,</span> <span class="s2">&#34;#listing a&#34;</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">map</span><span class="p">(</span><span class="n">results</span><span class="p">,</span> <span class="n">fn</span> <span class="p">{</span><span class="s2">&#34;a&#34;</span><span class="p">,</span> <span class="o">[</span><span class="p">{</span><span class="s2">&#34;href&#34;</span><span class="p">,</span> <span class="n">url</span><span class="p">}</span><span class="o">]</span><span class="p">,</span> <span class="n">_</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="n">url</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>No mesmo projeto ExMangaDownloadr temos este trecho:</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">process</span><span class="p">(</span><span class="n">manga_name</span><span class="p">,</span> <span class="n">url</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">File</span><span class="o">.</span><span class="n">mkdir_p!</span><span class="p">(</span><span class="n">directory</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">url</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">chapters</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">pages</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">images_sources</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">process_downloads</span><span class="p">(</span><span class="n">directory</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">optimize_images</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">compile_pdfs</span><span class="p">(</span><span class="n">manga_name</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="n">finish_process</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 acabamos de aprender que isso é o equivalente a fazer o seguinte (estou trapaceando um pouco porque as 3 funções finais do workflow não estão transformando o &ldquo;directory&rdquo; de entrada, só repassando):</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">process</span><span class="p">(</span><span class="n">manga_name</span><span class="p">,</span> <span class="n">url</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">File</span><span class="o">.</span><span class="n">mkdir_p!</span><span class="p">(</span><span class="n">directory</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">chapters</span>  <span class="o">=</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">chapters</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">pages</span>     <span class="o">=</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">pages</span><span class="p">(</span><span class="n">chapters</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">sources</span>   <span class="o">=</span> <span class="no">Workflow</span><span class="o">.</span><span class="n">images_sources</span><span class="p">(</span><span class="n">pages</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="no">Workflow</span><span class="o">.</span><span class="n">process_downloads</span><span class="p">(</span><span class="n">sources</span><span class="p">,</span> <span class="n">directory</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="no">Workflow</span><span class="o">.</span><span class="n">optimize_images</span><span class="p">(</span><span class="n">directory</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="no">Workflow</span><span class="o">.</span><span class="n">compile_pdfs</span><span class="p">(</span><span class="n">directory</span><span class="p">,</span> <span class="n">manga_name</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">finish_process</span><span class="p">(</span><span class="n">directory</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>Ou esta versão muito mais feia, que precisamos ler de trás pra frente:</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">process</span><span class="p">(</span><span class="n">manga_name</span><span class="p">,</span> <span class="n">url</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">File</span><span class="o">.</span><span class="n">mkdir_p!</span><span class="p">(</span><span class="n">directory</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">finish_process</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="no">Workflow</span><span class="o">.</span><span class="n">compile_pdfs</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="no">Workflow</span><span class="o">.</span><span class="n">optimize_images</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="no">Workflow</span><span class="o">.</span><span class="n">images_sources</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="no">Workflow</span><span class="o">.</span><span class="n">pages</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="no">Workflow</span><span class="o">.</span><span class="n">chapters</span><span class="p">(</span><span class="n">url</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="n">manga_name</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="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>Dá pra ver facilmente como o operador Pipe &ldquo;|&gt;&rdquo; deixa qualquer pipeline de transformação muito mais fácil de ler. Sempre que você está partindo de um valor, passando os resultados por uma <strong>cadeia de transformação</strong>, você vai usar esse operador.</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>Os conceitos apresentados neste artigo são os que eu acho que a maioria das pessoas vai achar mais desafiadores no primeiro contato. Se você entender Pattern Matching e Keyword Lists, vai entender todo o resto.</p>
<p>O site oficial oferece um ótimo <a href="http://elixir-lang.org/getting-started/introduction.html"target="_blank" rel="noopener">Getting Started</a> que você precisa ler inteiro.</p>
<p>Por intuição você já sabe a maior parte das coisas. Você tem blocos &ldquo;do .. end&rdquo;, mas ainda não sabe que eles são só macros de conveniência pra passar uma lista de instruções como argumento dentro de uma Keyword List. Os blocos a seguir são equivalentes:</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="kp">true</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">a</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">+</span> <span class="mi">2</span>
</span></span><span class="line"><span class="cl">  <span class="n">a</span> <span class="o">+</span> <span class="mi">10</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">if</span> <span class="kp">true</span><span class="p">,</span> <span class="k">do</span><span class="p">:</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">a</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">+</span> <span class="mi">2</span>
</span></span><span class="line"><span class="cl">  <span class="n">a</span> <span class="o">+</span> <span class="mi">10</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="k">if</span> <span class="kp">true</span><span class="p">,</span> <span class="o">[</span><span class="p">{</span><span class="ss">:do</span><span class="p">,</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">a</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">+</span> <span class="mi">2</span>
</span></span><span class="line"><span class="cl">  <span class="n">a</span> <span class="o">+</span> <span class="mi">10</span>
</span></span><span class="line"><span class="cl"><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>De cair o queixo, hein? Existem muitos macros que adicionam açúcar sintático em cima das primitivas.</p>
<p>Em geral, o Valim tornou as poderosas primitivas do Erlang mais acessíveis (Lists, Atoms, Maps, etc) e adicionou abstrações mais altas usando macros (blocos do .. end, o operador pipe, keyword lists, atalhos pra funções anônimas, etc). Essa combinação precisa é o que torna o Elixir muito gostoso de aprender. É como descascar uma cebola: você começa pelas abstrações mais altas e descobre macros de estruturas mais simples por baixo. Você vê uma Keyword List primeiro e descobre Lists de Tuples. Você vê um bloco e descobre outra Keyword List disfarçada por um macro. E assim vai.</p>
<p>Então você tem uma curva de entrada baixa e pode descer fundo na toca do coelho até o ponto em que está estendendo a própria linguagem.</p>
<p>Elixir oferece um design de linguagem muito esperto em cima do núcleo maduro do Erlang, com 25 anos. Isso é mais que esperto, é a escolha inteligente. Continue aprendendo!</p>
]]></content:encoded><category>tutoriais</category><category>elixir-e-erlang</category><category>programacao</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>Phoenix "15 Minute Blog": comparação com Ruby on Rails</title><link>https://akitaonrails.github.io/2015/11/20/phoenix-15-minute-blog-comparacao-com-ruby-on-rails/</link><guid isPermaLink="true">https://akitaonrails.github.io/2015/11/20/phoenix-15-minute-blog-comparacao-com-ruby-on-rails/</guid><pubDate>Fri, 20 Nov 2015 17:52:00 -0200</pubDate><description>&lt;p&gt;&lt;em&gt;Update 23/11/15:&lt;/em&gt; Chris McCord, criador do Phoenix, acabou de publicar um artigo explicando por que &lt;a href="https://dockyard.com/blog/2015/11/18/phoenix-is-not-rails"target="_blank" rel="noopener"&gt;&amp;ldquo;Phoenix is not Rails&amp;rdquo;&lt;/a&gt;. Ele entra em detalhes sobre várias coisas que descrevo neste artigo e recomendo fortemente que você leia também.&lt;/p&gt;
&lt;p&gt;Se você anda acompanhando a blogosfera de Elixir, é bem provável que tenha esbarrado na versão do clássico &amp;ldquo;15 minute blog&amp;rdquo; do &lt;a href="https://medium.com/@diamondgfx"target="_blank" rel="noopener"&gt;Brandon Richey&lt;/a&gt;. Se ainda não viu, você precisa ler pelo menos a &lt;a href="https://medium.com/@diamondgfx/introduction-fe138ac6079d#.ffl48saew"target="_blank" rel="noopener"&gt;Parte 1&lt;/a&gt; e a &lt;a href="https://t.co/DjGDek1ZOk"target="_blank" rel="noopener"&gt;Parte 2&lt;/a&gt;. É um tutorial bem detalhado que vai facilitar muito a sua entrada nas delícias do Phoenix.&lt;/p&gt;</description><content:encoded><![CDATA[<p><em>Update 23/11/15:</em> Chris McCord, criador do Phoenix, acabou de publicar um artigo explicando por que <a href="https://dockyard.com/blog/2015/11/18/phoenix-is-not-rails"target="_blank" rel="noopener">&ldquo;Phoenix is not Rails&rdquo;</a>. Ele entra em detalhes sobre várias coisas que descrevo neste artigo e recomendo fortemente que você leia também.</p>
<p>Se você anda acompanhando a blogosfera de Elixir, é bem provável que tenha esbarrado na versão do clássico &ldquo;15 minute blog&rdquo; do <a href="https://medium.com/@diamondgfx"target="_blank" rel="noopener">Brandon Richey</a>. Se ainda não viu, você precisa ler pelo menos a <a href="https://medium.com/@diamondgfx/introduction-fe138ac6079d#.ffl48saew"target="_blank" rel="noopener">Parte 1</a> e a <a href="https://t.co/DjGDek1ZOk"target="_blank" rel="noopener">Parte 2</a>. É um tutorial bem detalhado que vai facilitar muito a sua entrada nas delícias do Phoenix.</p>
<p>Este post é destinado a programadores Rails que querem saber como o Phoenix Framework se compara. Não é uma comparação completamente justa, já que se trata apenas do bom e velho exercício &ldquo;hello world&rdquo; com nested resources. Isso só arranha a superfície, mas serve como uma boa introdução.</p>
<p>Para o bem ou para o mal, muitos ainda consideram o Ruby on Rails como a melhor DSL para uma aplicação web. O Rails teve sucesso ao criar um vocabulário muito reconhecível para descrever cada componente de um projeto web. E eu vou argumentar que uma das forças do Phoenix está justamente em pegar emprestada essa mesma metáfora com sucesso. Isso definitivamente torna a curva de aprendizado bem mais suave.</p>
<p>Se você só quer clonar os exercícios e pular direto para o código, eu tenho o <a href="https://github.com/akitaonrails/pxblog_phoenix_exercise"target="_blank" rel="noopener">Pxblog em Phoenix</a> original e o equivalente <a href="https://github.com/akitaonrails/pxblog_rails_exercise"target="_blank" rel="noopener">Pxblog em Rails</a> para comparação.</p>
<h3>Começando: linha de comando e estrutura de pastas<span class="hx:absolute hx:-mt-20" id="começando-linha-de-comando-e-estrutura-de-pastas"></span>
    <a href="#come%c3%a7ando-linha-de-comando-e-estrutura-de-pastas" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Sem mais delongas, vamos começar comparando os comandos básicos do console:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>rails new pxblog
rails g scaffold Post title:string body:string
rails g scaffold User username:string email:string password_digest:string
rake db:create
rake db:migrate
rails g migration AddUserIdToPosts
rails 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>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>mix phoenix.new pxblog
mix phoenix.gen.html Post posts title:string body:string
mix phoenix.gen.html User users username:string email:string password_digest:string
mix ecto.create
mix ecto.migrate
mix ecto.gen.migration add_user_id_to_posts
mix phoenix.server
# iex -S mix phoenix.server para iniciar dentro do IEx e poder usar IEx.pry</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 cara já nos sentimos em casa. No mundo Rails temos o comando <tt>rails</tt> competindo com o tradicional gerenciador de tarefas <tt>rake</tt>. No lado do Phoenix, felizmente concentraram tudo sob o comando <tt>mix</tt> nativo do Elixir. Existem discussões para mover as tarefas do comando Rails para o Rake, onde elas pertencem, mas isso não vai acontecer tão cedo.</p>
<p>Como tudo está sob o domínio do Mix, você pode listar as tarefas relacionadas ao Phoenix assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>$ mix help | grep -i phoenix

mix phoenix.digest      # Digests and compress static files
mix phoenix.gen.channel # Generates a Phoenix channel
mix phoenix.gen.html    # Generates controller, model and views for an HTML based resource
mix phoenix.gen.json    # Generates a controller and model for a JSON based resource
mix phoenix.gen.model   # Generates an Ecto model
mix phoenix.gen.secret  # Generates a secret
mix phoenix.new         # Create a new Phoenix v1.0.3 application
mix phoenix.routes      # Prints all routes
mix phoenix.server      # Starts applications and their servers</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 estrutura de diretórios que o <tt>phoenix.new</tt> gera é um pouco diferente do Rails:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>/_build              # binários que o mix compila (ignore)
/config
- config.exs         # pense no /config/application.rb do Rails
- dev.exs            # pense no /config/environments/development.rb do Rails
- prod.exs           # pense no /config/environments/production.rb do Rails
- prod.secret.exs    # pense no /config/secrets.yml do Rails
- test.exs           # pense no /config/environments/test.rb do Rails
/deps                # onde o mix deps.get coloca as dependências
/lib
  /pxblog
    - endpoint.ex    # algo como config.ru e application.rb
    - repo.ex        # configuração do Ecto
  - pxblog.ex        # onde você configura a árvore de supervisão das apps OTP
/node_modules        # Phoenix integra com Node.js
/priv
  /repo
    /migrations      # pense no /db/migrate do Rails
    - seeds.exs      # pense no /db/seeds.rb do Rails
  /static            # pense no /public do Rails
    /css
    /images
    /js
/test                # pense no /test do Rails
  /channels
  /controllers
  /models
  /support
  /views
  - test_helper.exs  # pense no /test/test_helper.rb do Rails
/web                 # pense no /app do Rails
  /channels          # pense nos channels do ActionCable do Rails 5
  /controllers
  /models
  /static            # pense no /app/assets do Rails
    /assets
      /images
      /css
      /js
      /vendor
  /templates         # pense no /app/views do Rails
    /layout
  /views             # pense no /app/helpers do Rails, mas com Presenters
    - layout_view.ex
  - router.ex        # pense no /config/routes.rb do Rails
  - web.ex           # macros para configurar cada componente MVC
- .gitignore
- README.md
- brunch-config.js  # o reload do front-end em dev é controlado pelo Brunch
- mix.exs           # pense no Gemfile do Ruby (com extras)
- package.json      # dependências do Node.js</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>No Rails, o ponto de partida é a aplicação Rackup <tt>config.ru</tt> (já que o Rails virou uma aplicação Rack desde a versão 3.0). Ele então carrega o <tt>config/environment.rb</tt>, depois <tt>config/application.rb</tt>, depois <tt>config/boot.rb</tt> que carrega as gems declaradas no <tt>Gemfile</tt>, junto com <tt>config/initializers/*.rb</tt> e cada arquivo em <tt>config/environments</tt>, configurando o Rails::Application e plugando a configuração, o pipeline de Rack Middlewares.</p>
<p>No Phoenix, o ponto de partida — como em qualquer aplicação Elixir — é o arquivo <tt>mix.exs</tt>. Neste caso ele aponta para a app OTP/Phoenix &ldquo;Pxblog&rdquo; definida em <tt>lib/pxblog/pxblog.ex</tt>. Por sua vez ela inicia e supervisiona as apps &ldquo;Pxblog.Endpoint&rdquo; e &ldquo;Pxblog.Repo&rdquo;, definidas em <tt>lib/pxblog/endpoint.ex</tt> e <tt>lib/pxblog/repo.ex</tt>, respectivamente.</p>
<p>Se você criar outras apps OTP, é aqui que você adiciona à Árvore de Supervisão OTP. Eu diria que é algo parecido com uma Rails Engine, embora tecnicamente não seja a mesma coisa, mas talvez essa metáfora ajude.</p>
<p>Um aspecto importante do Rails é como ele divide as configurações de development, test e production em arquivos diferentes. O Phoenix faz o mesmo nos arquivos <tt>dev.exs</tt>, <tt>test.exs</tt> e <tt>prod.exs</tt>. Na verdade, isso é uma <a href="http://elixir-lang.org/getting-started/mix-otp/introduction-to-mix.html#environments"target="_blank" rel="noopener">feature do Mix</a>. O Mix é uma versão mais elaborada do Rake, e faz sentido, já que José Valim também tentou empurrar o Thor para substituir o Rake, embora isso nunca tenha pegado tração na comunidade Ruby (o Rake já está enraizado em todo lugar). Frameworks web que não impõem separação de ambientes por padrão, nessa altura do campeonato, são inúteis. O legal é que toda app Elixir gerada pelo Mix herda essa feature útil.</p>
<p>Em vez de ter um <tt>database.yml</tt>, a configuração de banco de dados está espalhada pelos arquivos de configuração de ambiente, e as configurações de produção ficam em um arquivo separado <tt>prod.secret.exs</tt>, que obviamente fica ignorado no <tt>.gitignore</tt>, como o arquivo <tt>secrets.yml</tt> do Rails.</p>
<h3>Estrutura MVC<span class="hx:absolute hx:-mt-20" id="estrutura-mvc"></span>
    <a href="#estrutura-mvc" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Você vai notar que cada elemento do MVC do Phoenix começa 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/page_controller.ex</span>
</span></span><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">Pxblog</span><span class="o">.</span><span class="n">PageController</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">Pxblog</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 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="c1"># web/models/post.ex</span>
</span></span><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">Pxblog</span><span class="o">.</span><span class="n">Post</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">Pxblog</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="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="c1"># web/views/post_view.ex</span>
</span></span><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">Pxblog</span><span class="o">.</span><span class="n">PostView</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">Pxblog</span><span class="o">.</span><span class="n">Web</span><span class="p">,</span> <span class="ss">:view</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><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">Pxblog</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">Pxblog</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 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>Esse módulo <tt>Pxblog.Web</tt> é definido em <tt>web/web.ex</tt> 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/web.ex</span>
</span></span><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">Pxblog</span><span class="o">.</span><span class="n">Web</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">model</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">quote</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">Model</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">import</span> <span class="no">Ecto</span><span class="o">.</span><span class="n">Changeset</span>
</span></span><span class="line"><span class="cl">      <span class="n">import</span> <span class="no">Ecto</span><span class="o">.</span><span class="n">Query</span><span class="p">,</span> <span class="ss">only</span><span class="p">:</span> <span class="o">[</span><span class="ss">from</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="ss">from</span><span class="p">:</span> <span class="mi">2</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></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">controller</span> <span class="k">do</span> <span class="o">...</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">view</span> <span class="k">do</span> <span class="o">...</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">router</span> <span class="k">do</span> <span class="o">...</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">channel</span> <span class="k">do</span> <span class="o">...</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="vi">@doc</span> <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">  When used, dispatch to the appropriate controller/view/etc.
</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="n">defmacro</span> <span class="n">__using__</span><span class="p">(</span><span class="n">which</span><span class="p">)</span> <span class="k">when</span> <span class="n">is_atom</span><span class="p">(</span><span class="n">which</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">apply</span><span class="p">(</span><span class="n">__MODULE__</span><span class="p">,</span> <span class="n">which</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 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ê ainda não estudou, esse é um bom momento para aprender sobre <a href="http://elixir-lang.org/getting-started/meta/macros.html"target="_blank" rel="noopener">Macros do Elixir</a>. Pense no código dentro do bloco <tt>quote</tt> como sendo &ldquo;injetado&rdquo; em cada módulo que chama <tt>use Pxblog.Web</tt>. Quando você dá <tt>use</tt> em um módulo, ele chama o macro <tt><strong>using</strong></tt>. Pense nisso como um Module Mixin do Ruby chamando o callback <tt>included</tt> e executando um <tt>class_eval</tt>. Como não existe o conceito de Classes e subclasses, incluímos mixins em cada classe para adquirir os comportamentos desejados.</p>
<h3>MVC Router<span class="hx:absolute hx:-mt-20" id="mvc-router"></span>
    <a href="#mvc-router" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Diferente do <tt>config/routes.rb</tt> do Rails, que define rotas, o <tt>web/router.ex</tt> define não só as rotas em si, mas também pipelines de transformação e estratégias de roteamento:</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">Pxblog</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">Pxblog</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="n">plug</span> <span class="ss">: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></span><span class="line"><span class="cl">  <span class="n">pipeline</span> <span class="ss">:api</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;json&#34;</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">scope</span> <span class="s2">&#34;/&#34;</span><span class="p">,</span> <span class="no">Pxblog</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">resources</span> <span class="s2">&#34;/users&#34;</span><span class="p">,</span> <span class="no">UserController</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="n">resources</span> <span class="s2">&#34;/posts&#34;</span><span class="p">,</span> <span class="no">PostController</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">resources</span> <span class="s2">&#34;/sessions&#34;</span><span class="p">,</span> <span class="no">SessionController</span><span class="p">,</span> <span class="ss">only</span><span class="p">:</span> <span class="o">[</span><span class="ss">:new</span><span class="p">,</span> <span class="ss">:create</span><span class="p">,</span> <span class="ss">:delete</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="c1"># Other scopes may use custom stacks.</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># scope &#34;/api&#34;, Pxblog do</span>
</span></span><span class="line"><span class="cl">  <span class="c1">#   pipe_through :api</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># 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>Você tem que ler assim:</p>
<p>O bloco <tt>pipeline</tt> pluga &ldquo;filtros&rdquo;. Eles são parecidos com os Rack middlewares que definimos em <tt>config/application.rb</tt> no Rails. Isso é muito esperto, porque uma diferença chave entre o Ruby on Rails padrão e o <a href="https://github.com/rails-api/rails-api"target="_blank" rel="noopener">projeto Rails-API</a> é justamente a remoção de Rack middlewares desnecessários que endpoints de API não precisam.</p>
<p>No Phoenix podemos definir um pipeline para navegadores web e outro para clientes de API, e você vê que a diferença é a remoção de Plugs.</p>
<p>Depois definimos os scopes baseados nos paths raiz. Tem um scope &ldquo;/&rdquo; que se conecta ao pipeline <tt>:browser</tt> e um scope opcional (comentado) &ldquo;/api&rdquo; que passa pelo pipeline <tt>:api</tt>.</p>
<p>Dentro do bloco scope, é muito parecido com a DSL Restful para definir rotas, que de novo é uma boa adaptação das Rotas do Rails. <a href="http://www.phoenixframework.org/docs/routing"target="_blank" rel="noopener">Leia a documentação</a> para saber os detalhes.</p>
<p>Também parecido com as Rotas do Rails, ele gera os URL helpers apropriados que ficam disponíveis nos Controllers, Views e Templates. Vamos começar vendo alguns URL helpers do Rails em 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-html" data-lang="html"><span class="line"><span class="cl"><span class="c">&lt;!-- app/views/posts/edit.html.erb --&gt;</span>
</span></span><span class="line"><span class="cl"><span class="err">&lt;</span>%= link_to &#39;Show&#39;, [@user, post] %&gt;
</span></span><span class="line"><span class="cl"><span class="err">&lt;</span>%= link_to &#39;Edit&#39;, edit_user_post_path(@user, post) %&gt;
</span></span><span class="line"><span class="cl"><span class="err">&lt;</span>%= link_to &#39;Destroy&#39;, [@user, post], method: :delete, data: { confirm: &#39;Are you sure?&#39; } %&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>E no Phoenix:</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;!-- web/templates/post/edit.html.eex --&gt;</span>
</span></span><span class="line"><span class="cl"><span class="err">&lt;</span>%= link &#34;Show&#34;, to: user_post_path(@conn, :show, @user, post) %&gt;
</span></span><span class="line"><span class="cl"><span class="err">&lt;</span>%= link &#34;Edit&#34;, to: user_post_path(@conn, :edit, @user, post) %&gt;
</span></span><span class="line"><span class="cl"><span class="err">&lt;</span>%= link &#34;Delete&#34;, to: user_post_path(@conn, :delete, @user, post), method: :delete, data: [confirm: &#34;Are you sure?&#34;] %&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>O URL helper principal do Rails consegue receber um Array como <tt>[@user, post]</tt> e executá-lo da mesma forma como se tivéssemos escrito <tt>user_post_path(@user, post)</tt>. Uma diferença em relação ao Rails é que, em vez de criar um helper para cada verbo HTTP, você tem só um helper por recurso que aceita um parâmetro extra para indicar o verbo. Então temos <tt>post_path(@conn, :edit, post)</tt> em vez do jeito Rails <tt>edit_post_path(post)</tt>.</p>
<p>Assim como nos middlewares do Rails, um pipeline recebe a conexão da requisição e a passa por um pipe, transformando seus metadados para que ela fique limpa e usável dentro dos nossos controllers.</p>
<h3>MVC Controller<span class="hx:absolute hx:-mt-20" id="mvc-controller"></span>
    <a href="#mvc-controller" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>No Phoenix começamos um controller 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/post_controller.ex</span>
</span></span><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">Pxblog</span><span class="o">.</span><span class="n">PostController</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">Pxblog</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">Pxblog</span><span class="o">.</span><span class="n">Post</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">:scrub_params</span><span class="p">,</span> <span class="s2">&#34;post&#34;</span> <span class="k">when</span> <span class="n">action</span> <span class="k">in</span> <span class="o">[</span><span class="ss">:create</span><span class="p">,</span> <span class="ss">:update</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">  <span class="n">plug</span> <span class="ss">:assign_user</span>
</span></span><span class="line"><span class="cl">  <span class="n">plug</span> <span class="ss">:authorize_user</span> <span class="k">when</span> <span class="n">action</span> <span class="k">in</span> <span class="o">[</span><span class="ss">:new</span><span class="p">,</span> <span class="ss">:create</span><span class="p">,</span> <span class="ss">:update</span><span class="p">,</span> <span class="ss">:edit</span><span class="p">,</span> <span class="ss">:delete</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>É parecido com este setup de controller Rails:</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/posts_controller.rb</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">PostsController</span> <span class="o">&lt;</span> <span class="no">ApplicationController</span>
</span></span><span class="line"><span class="cl">  <span class="n">before_action</span> <span class="ss">:assign_user</span>
</span></span><span class="line"><span class="cl">  <span class="n">before_action</span> <span class="ss">:authorize_user</span><span class="p">,</span> <span class="ss">only</span><span class="p">:</span> <span class="o">[</span><span class="ss">:new</span><span class="p">,</span> <span class="ss">:create</span><span class="p">,</span> <span class="ss">:update</span><span class="p">,</span> <span class="ss">:edit</span><span class="p">,</span> <span class="ss">:destroy</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>Como você deve ter concluído, uma chamada <tt>plug</tt> funciona um pouco como um pipeline de <tt>before_action</tt>. O <tt>scrub_params</tt> eu acredito que seja parecido com o <tt>ActionDispatch::ParamParser</tt> do Rails, mas não tenho certeza; sei que ele limpa valores de string vazios, transformando em nil, para que você não atualize seus modelos desnecessariamente.</p>
<p>Mas, diferente do Rails, onde uma chamada a <tt>redirect_to</tt> interrompe o pipeline, aqui precisamos parar o pipeline explicitamente 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">assign_user</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="sx">%{&#34;user_id&#34; =&gt; user_id}</span> <span class="o">=</span> <span class="n">conn</span><span class="o">.</span><span class="n">params</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="n">user</span> <span class="o">=</span> <span class="no">Repo</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="no">Pxblog</span><span class="o">.</span><span class="n">User</span><span class="p">,</span> <span class="n">user_id</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">assign</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="ss">:user</span><span class="p">,</span> <span class="n">user</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">conn</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="n">put_flash</span><span class="p">(</span><span class="ss">:error</span><span class="p">,</span> <span class="s2">&#34;Invalid user!&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="n">redirect</span><span class="p">(</span><span class="ss">to</span><span class="p">:</span> <span class="n">page_path</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="ss">:index</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="n">halt</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>No Phoenix, tudo gira em torno de um pipeline de transformação da conexão de requisição que você começa a configurar nos Plugs do Router, nos Plugs do Controller e nas actions do Controller. Todos eles recebem a conexão do passo anterior e devolvem uma conexão transformada para o próximo passo, até que ela se torne uma resposta HTTP apropriada. Diferente do Rails, esse caminho é muito mais explícito, e você sabe que vai receber essa conexão e deve aplicar transformações nela até renderizar o HTML final ou enviar de volta algum header de erro.</p>
<p>Para ver o quão explícito, vamos começar com uma action típica de controller Rails:</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">destroy</span>
</span></span><span class="line"><span class="cl">  <span class="vi">@user</span> <span class="o">=</span> <span class="no">User</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="n">params</span><span class="o">[</span><span class="ss">:id</span><span class="o">]</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="vi">@user</span><span class="o">.</span><span class="n">destroy</span>
</span></span><span class="line"><span class="cl">  <span class="n">respond_to</span> <span class="k">do</span> <span class="o">|</span><span class="nb">format</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">    <span class="nb">format</span><span class="o">.</span><span class="n">html</span> <span class="p">{</span> <span class="n">redirect_to</span> <span class="n">users_url</span><span class="p">,</span> <span class="ss">notice</span><span class="p">:</span> <span class="s1">&#39;User was successfully destroyed.&#39;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nb">format</span><span class="o">.</span><span class="n">json</span> <span class="p">{</span> <span class="n">head</span> <span class="ss">:no_content</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>Antigamente, definir a mensagem flash e redirecionar eram 2 métodos diferentes; versões mais recentes mesclaram os dois por conveniência. O Rails também tem o conceito de Responders, que o Phoenix ainda não tem (#OpportunityToContribute!).</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">delete</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="sx">%{&#34;id&#34; =&gt; id}</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">user</span> <span class="o">=</span> <span class="no">Repo</span><span class="o">.</span><span class="n">get!</span><span class="p">(</span><span class="no">User</span><span class="p">,</span> <span class="nb">id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="no">Repo</span><span class="o">.</span><span class="n">delete!</span><span class="p">(</span><span class="n">user</span><span class="p">)</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="n">put_flash</span><span class="p">(</span><span class="ss">:info</span><span class="p">,</span> <span class="s2">&#34;User deleted successfully.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="n">redirect</span><span class="p">(</span><span class="ss">to</span><span class="p">:</span> <span class="n">user_path</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="ss">:index</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>Você pode ver que, deixando os Responders de lado, a versão Phoenix é <strong>notavelmente</strong> parecida. E é assim para todas as ações Restful do controller. Mas mais parecido com o falecido Merb, cada action de Controller no Phoenix tem uma assinatura de função apropriada declarando os parâmetros que ela espera receber, em vez de ter um hash global <tt>params</tt> que precisa passar pelos <a href="https://github.com/rails/strong_parameters"target="_blank" rel="noopener">Strong Parameters</a>.</p>
<p>Para variações diferentes dos mesmos parâmetros, você pode declarar várias funções com o mesmo nome mas com argumentos diferentes para fazer pattern match, como no exemplo do <tt>SessionController</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/session_controller.ex</span>
</span></span><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">Pxblog</span><span class="o">.</span><span class="n">SessionController</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">Pxblog</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 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;user&#34; =&gt; %{&#34;username&#34; =&gt; username, &#34;password&#34; =&gt; password}}</span><span class="p">)</span> <span class="k">when</span> <span class="ow">not</span> <span class="n">is_nil</span><span class="p">(</span><span class="n">username</span><span class="p">)</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">is_nil</span><span class="p">(</span><span class="n">password</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">user</span> <span class="o">=</span> <span class="no">Repo</span><span class="o">.</span><span class="n">get_by</span><span class="p">(</span><span class="no">User</span><span class="p">,</span> <span class="ss">username</span><span class="p">:</span> <span class="n">username</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">sign_in</span><span class="p">(</span><span class="n">user</span><span class="p">,</span> <span class="n">password</span><span class="p">,</span> <span class="n">conn</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">create</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">failed_login</span><span class="p">(</span><span class="n">conn</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>Aqui temos a mesma função <tt>create/2</tt> com pattern matching e guards. A segunda versão recebe qualquer coisa, caso a primeira versão falhe ao fazer pattern match contra os parâmetros recebidos. O Phoenix espera mais ou menos a mesma estrutura de parâmetros que o Rails, então é bem intuitivo de acompanhar.</p>
<h3>MVC Models<span class="hx:absolute hx:-mt-20" id="mvc-models"></span>
    <a href="#mvc-models" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Em vez do bom e velho ActiveRecord (ActiveModel), no Phoenix temos que lidar com os <a href="http://www.phoenixframework.org/docs/ecto-models"target="_blank" rel="noopener">Modelos Ecto</a>. Ele já suporta Postgresql, MySQL, Sqlite3, MongoDB, então você está coberto em 99% dos casos.</p>
<p>O Ecto separa a Lógica do Modelo do Gerenciamento de Persistência. Em vez de usar o padrão de design Active Record, ele prefere o padrão Data Mapper. Essa é uma discussão antiga entre os Railers. Muita gente reclama que a lógica de persistência fica junto com a lógica de negócio, e da quantidade de mágica e metaprogramação que torna o ActiveRecord muito fácil de começar mas muito difícil de dominar de verdade.</p>
<p>Você deve ler o post do José Valim sobre <a href="http://blog.plataformatec.com.br/2015/08/working-with-ecto-associations-and-embeds/"target="_blank" rel="noopener">Associações no Ecto</a> para começar. Mas por enquanto, vamos comparar modelos simples de Rails e Ecto:</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/user.rb</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">User</span> <span class="o">&lt;</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span>
</span></span><span class="line"><span class="cl">  <span class="n">has_secure_password</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">has_many</span> <span class="ss">:posts</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">validates</span> <span class="ss">:username</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 class="n">validates</span> <span class="ss">:email</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 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 Rails temos o <a href="http://api.rubyonrails.org/classes/ActiveModel/SecurePassword/ClassMethods.html"target="_blank" rel="noopener"><tt>ActiveSupport#has_secure_password</tt></a>, que usa BCrypt por baixo dos panos para gerar um digest de senha apropriado. Se você está construindo autenticação do zero, você <strong>deve</strong> usar essa construção.</p>
<p>O Phoenix ainda não tem essa mesma feature (#OpportunityToContribute!), então a versão dele é um pouco mais verbosa para dar conta da lógica do digest BCrypt usando a biblioteca de hashing de senhas <a href="https://github.com/elixircnx/comeonin"target="_blank" rel="noopener">Comeonin</a>. Vamos por partes:</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/user.ex</span>
</span></span><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">Pxblog</span><span class="o">.</span><span class="n">User</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">Pxblog</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="n">import</span> <span class="no">Comeonin</span><span class="o">.</span><span class="n">Bcrypt</span><span class="p">,</span> <span class="ss">only</span><span class="p">:</span> <span class="o">[</span><span class="ss">hashpwsalt</span><span class="p">:</span> <span class="mi">1</span><span class="o">]</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;users&#34;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">field</span> <span class="ss">:username</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">:email</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">:password_digest</span><span class="p">,</span> <span class="ss">:string</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></span><span class="line"><span class="cl">    <span class="c1"># Virtual Fields</span>
</span></span><span class="line"><span class="cl">    <span class="n">field</span> <span class="ss">:password</span><span class="p">,</span> <span class="ss">:string</span><span class="p">,</span> <span class="ss">virtual</span><span class="p">:</span> <span class="kp">true</span>
</span></span><span class="line"><span class="cl">    <span class="n">field</span> <span class="ss">:password_confirmation</span><span class="p">,</span> <span class="ss">:string</span><span class="p">,</span> <span class="ss">virtual</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">has_many</span> <span class="ss">:posts</span><span class="p">,</span> <span class="no">Pxblog</span><span class="o">.</span><span class="n">Post</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 primeira parte dos Modelos Ecto declara os campos do banco de dados, campos virtuais e associações. O ActiveRecord do Rails prefere a abordagem de pedir ao banco que envie os metadados da tabela e usa metaprogramação para criar todos os campos depois, em runtime. Muita gente não gosta dessa abordagem, e essa é a alternativa: declaração explícita.</p>
<p>Estamos mapeando o módulo <tt>User</tt> com a tabela <tt>&lsquo;users&rsquo;</tt> do banco de dados na declaração <tt>schema &ldquo;users&rdquo; do</tt>, em vez de recorrer a convenções de pluralização.</p>
<p>A última linha tem nossa conhecida associação <tt>has_many</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">Pxblog</span><span class="o">.</span><span class="n">User</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="vi">@required_fields</span> <span class="o">~</span><span class="n">w</span><span class="p">(</span><span class="n">username</span> <span class="n">email</span> <span class="n">password</span> <span class="n">password_confirmation</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><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>Aqui declaramos os campos obrigatórios; isso é só uma variável com uma lista de campos, e não as validações em si. Isso vai ser usado no próximo passo para fazer algo similar a <tt>validates :username, presence: true</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">Pxblog</span><span class="o">.</span><span class="n">User</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="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">hash_password</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>Em vez de fazer algo como <tt>Post.create(params)</tt>, primeiro criamos um changeset e depois passamos para o Repositório principal do Ecto. O Repositório então fica responsável pela parte da persistência. O Modelo Ecto fica responsável por validar e limpar o changeset que o Repositório recebe.</p>
<p>O <tt>changeset/2</tt> retorna uma <a href="http://elixir-lang.org/getting-started/structs.html"target="_blank" rel="noopener">Struct do Elixir</a> para trabalharmos antes de passar para a aplicação Repositório fazer a persistência.</p>
<p>Nessa função podemos declarar um pipeline de validações, constraints e outras transformações de atributos. Por exemplo, plugamos uma função <tt>hash_password/2</tt> que vai pegar o valor em <tt>password</tt> e usar o <tt>Comeonin.hashpwsalt/1</tt> para transformar a string da senha em um digest bcrypt e armazenar no atributo password_digest:</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">Pxblog</span><span class="o">.</span><span class="n">User</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="n">defp</span> <span class="n">hash_password</span><span class="p">(</span><span class="n">changeset</span><span class="p">)</span> <span class="k">do</span> <span class="o">...</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">password</span> <span class="o">=</span> <span class="n">get_change</span><span class="p">(</span><span class="n">changeset</span><span class="p">,</span> <span class="ss">:password</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="n">changeset</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">:password_digest</span><span class="p">,</span> <span class="n">hashpwsalt</span><span class="p">(</span><span class="n">password</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">changeset</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>E retornamos o changeset transformado, para que o pipeline pegue e passe adiante para outros plugs, como validações. Se quiséssemos adicionar mais validações, daria para 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">defmodule</span> <span class="no">Pxblog</span><span class="o">.</span><span class="n">User</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="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">:password</span><span class="p">,</span> <span class="ss">min</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span> <span class="ss">max</span><span class="p">:</span> <span class="mi">100</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">:username</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">50</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="n">validate_confirmation</span><span class="p">(</span><span class="ss">:password</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">:username</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="n">hash_password</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>Pronto. E no controller, a função <tt>update/2</tt>, por exemplo, vai usar o changeset 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">update</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="sx">%{&#34;id&#34; =&gt; id, &#34;user&#34; =&gt; user_params}</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">user</span> <span class="o">=</span> <span class="no">Repo</span><span class="o">.</span><span class="n">get!</span><span class="p">(</span><span class="no">User</span><span class="p">,</span> <span class="nb">id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">changeset</span> <span class="o">=</span> <span class="no">User</span><span class="o">.</span><span class="n">changeset</span><span class="p">(</span><span class="n">user</span><span class="p">,</span> <span class="n">user_params</span><span class="p">)</span>
</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">update</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="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>Na segunda linha usamos o Repositório para consultar o schema &lsquo;users&rsquo; como declarado no modelo <tt>User</tt>.</p>
<p>Depois, transformamos o struct <tt>user</tt> com o map <tt>user_params</tt> que recebemos do pipeline do Router, como definido na primeira linha.</p>
<p>A transformação retorna um changeset, que vai conter as mensagens de erro. Aí passamos o changeset de novo para o Repositório, para que ele atualize o registro na tabela.</p>
<h3>MVC View e Templates<span class="hx:absolute hx:-mt-20" id="mvc-view-e-templates"></span>
    <a href="#mvc-view-e-templates" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>No caso da função <tt>edit/2</tt>, chamamos a função <tt>render/3</tt> 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/views/user_view.ex</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">edit</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="sx">%{&#34;id&#34; =&gt; id}</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">user</span> <span class="o">=</span> <span class="no">Repo</span><span class="o">.</span><span class="n">get!</span><span class="p">(</span><span class="no">User</span><span class="p">,</span> <span class="nb">id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">changeset</span> <span class="o">=</span> <span class="no">User</span><span class="o">.</span><span class="n">changeset</span><span class="p">(</span><span class="n">user</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">render</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="s2">&#34;edit.html&#34;</span><span class="p">,</span> <span class="ss">user</span><span class="p">:</span> <span class="n">user</span><span class="p">,</span> <span class="ss">changeset</span><span class="p">:</span> <span class="n">changeset</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>Isso primeiro chama o <tt>web/views/user.ex</tt>, que importa coisas como helpers, transforma as variáveis <tt>user</tt> e <tt>changeset</tt> em <a href="http://elixir-lang.org/getting-started/module-attributes.html"target="_blank" rel="noopener">atributos de módulo</a> (aqueles que começam com &ldquo;@&rdquo;, caso você estivesse se perguntando o que são). E a View sabe encontrar o template <tt>edit.html</tt> em <tt>web/templates/user/edit.html.eex</tt> porque está dito assim em <tt>web/web.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="c1"># web/web.ex</span>
</span></span><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">Pxblog</span><span class="o">.</span><span class="n">Web</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="k">def</span> <span class="nf">view</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">quote</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">View</span><span class="p">,</span> <span class="ss">root</span><span class="p">:</span> <span class="s2">&#34;web/templates&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="c1"># Import convenience functions from controllers</span>
</span></span><span class="line"><span class="cl">      <span class="n">import</span> <span class="no">Phoenix</span><span class="o">.</span><span class="n">Controller</span><span class="p">,</span> <span class="ss">only</span><span class="p">:</span> <span class="o">[</span><span class="ss">get_csrf_token</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="ss">get_flash</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="ss">view_module</span><span class="p">:</span> <span class="mi">1</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="c1"># Use all HTML functionality (forms, tags, etc)</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">HTML</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">import</span> <span class="no">Pxblog</span><span class="o">.</span><span class="n">Router</span><span class="o">.</span><span class="n">Helpers</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="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 não copiei e colei todos os outros macros em <tt>web/web.ex</tt>, mas dê uma olhada para ver o que models, controllers, router e channel importam em cada módulo que você cria.</p>
<p>No Rails temos o ERB padrão, de &ldquo;Embedded Ruby&rdquo;, e no Phoenix temos o &ldquo;EEX&rdquo;, de &ldquo;Embedded Elixir&rdquo;. É essencialmente a mesma coisa: um template HTML que aceita trechos de código Elixir entre <tt>&lt;%= &hellip; %&gt;</tt>. Então o template <tt>edit.html.eex</tt> 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-html" data-lang="html"><span class="line"><span class="cl"><span class="c">&lt;!-- app/views/users/edit.html.erb --&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">h2</span><span class="p">&gt;</span>Edit user<span class="p">&lt;/</span><span class="nt">h2</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="err">&lt;</span>%= render &#34;form.html&#34;, changeset: @changeset,
</span></span><span class="line"><span class="cl">                        action: user_path(@conn, :update, @user) %&gt;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="err">&lt;</span>%= link &#39;Back&#39;, to: user_path(@conn, :show, @user) %&gt; |
</span></span><span class="line"><span class="cl"><span class="err">&lt;</span>%= link &#34;Back&#34;, to: user_path(@conn, :index) %&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>Que é muito parecido com o equivalente <tt>edit.html.erb</tt> em Rails:</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;!-- web/templates/user/edit.html.eex --&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>Editing User<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="err">&lt;</span>%= render &#39;form&#39; %&gt;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="err">&lt;</span>%= link_to &#39;Show&#39;, @user %&gt; |
</span></span><span class="line"><span class="cl"><span class="err">&lt;</span>%= link_to &#39;Back&#39;, users_path %&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>A versão Phoenix é um pouco mais verbosa, propositalmente para não esconder tanta coisa quanto o Rails esconde. Dá para discutir se mais ou menos mágica torna o framework mais produtivo, mas a versão Phoenix, sendo mais explícita, deixa um rastro de migalhas mais fácil de seguir, especialmente se você está começando. Aqui não temos o conceito de &ldquo;partials&rdquo;; qualquer template pode renderizar qualquer outro template, só precisamos passar a variável necessária para o template funcionar. Mas, em vez de passar uma instância de modelo, estamos passando um changeset.</p>
<p>O template &ldquo;form.html&rdquo; também é bem parecido. Vamos ver primeiro a versão Phoenix:</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;!-- web/templates/user/edit.html.eex --&gt;</span>
</span></span><span class="line"><span class="cl"><span class="err">&lt;</span>%= form_for @changeset, @action, fn f -&gt; %&gt;
</span></span><span class="line"><span class="cl">  <span class="err">&lt;</span>%= if @changeset.action do %&gt;
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;alert alert-danger&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span>Oops, something went wrong! Please check the errors below:<span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">ul</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="err">&lt;</span>%= for {attr, message} <span class="p">&lt;</span><span class="nt">-</span> <span class="na">f</span><span class="err">.</span><span class="na">errors</span> <span class="na">do</span> <span class="err">%</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">          <span class="p">&lt;</span><span class="nt">li</span><span class="p">&gt;</span><span class="err">&lt;</span>%= humanize(attr) %&gt; <span class="err">&lt;</span>%= message %&gt;<span class="p">&lt;/</span><span class="nt">li</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="err">&lt;</span>% end %&gt;
</span></span><span class="line"><span class="cl">      <span class="p">&lt;/</span><span class="nt">ul</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="err">&lt;</span>% end %&gt;
</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">class</span><span class="o">=</span><span class="s">&#34;form-group&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="err">&lt;</span>%= label f, :username, &#34;Username&#34;, class: &#34;control-label&#34; %&gt;
</span></span><span class="line"><span class="cl">    <span class="err">&lt;</span>%= text_input f, :username, class: &#34;form-control&#34; %&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></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 agora a versão ERB do Rails:</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/users/_form.html.erb --&gt;</span>
</span></span><span class="line"><span class="cl"><span class="err">&lt;</span>%= form_for(@user) do |f| %&gt;
</span></span><span class="line"><span class="cl">  <span class="err">&lt;</span>% if @user.errors.any? %&gt;
</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;error_explanation&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">h2</span><span class="p">&gt;</span><span class="err">&lt;</span>%= pluralize(@user.errors.count, &#34;error&#34;) %&gt; prohibited this user from being saved:<span class="p">&lt;/</span><span class="nt">h2</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">ul</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="err">&lt;</span>% @user.errors.full_messages.each do |message| %&gt;
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">li</span><span class="p">&gt;</span><span class="err">&lt;</span>%= message %&gt;<span class="p">&lt;/</span><span class="nt">li</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="err">&lt;</span>% end %&gt;
</span></span><span class="line"><span class="cl">      <span class="p">&lt;/</span><span class="nt">ul</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="err">&lt;</span>% end %&gt;
</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">class</span><span class="o">=</span><span class="s">&#34;field&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="err">&lt;</span>%= f.label :username %&gt;<span class="p">&lt;</span><span class="nt">br</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="err">&lt;</span>%= f.text_field :username %&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></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>Notavelmente parecidos. Existem coisas específicas de cada linguagem, como ter <tt>label(f, :username)</tt> em vez de <tt>f.label :username</tt>. Como em Elixir os parênteses também são opcionais e como o Phoenix implementa helpers muito parecidos com a versão Rails, como o &ldquo;form_for&rdquo;, a gente se sente confortável muito rápido.</p>
<p>O Rails tem um layout padrão em <tt>app/views/layouts/application.html.erb</tt> e o Phoenix tem um layout padrão em <tt>web/templates/layout/app.html.eex</tt>. O resto é bem a mesma coisa.</p>
<p>O <tt>mix phoenix.gen.html</tt> cria uma estrutura de templates parecida com a do comando <tt>rails generate scaffold</tt>.</p>
<p>O que o Phoenix chama de &ldquo;views&rdquo; é mais parecido com o que o Rails chama de &ldquo;helpers&rdquo;. Podemos usá-las de forma semelhante; por exemplo, para acessar a sessão do usuário atual, fazemos assim no Phoenix em <tt>web/views/layout_view.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="c1"># web/views/layout_view.ex</span>
</span></span><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">Pxblog</span><span class="o">.</span><span class="n">LayoutView</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">Pxblog</span><span class="o">.</span><span class="n">Web</span><span class="p">,</span> <span class="ss">:view</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">current_user</span><span class="p">(</span><span class="n">conn</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="no">Plug</span><span class="o">.</span><span class="n">Conn</span><span class="o">.</span><span class="n">get_session</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="ss">:current_user</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>Que é praticamente igual ao <tt>app/helpers/application.rb</tt> do Rails:</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/helpers/application.rb</span>
</span></span><span class="line"><span class="cl"><span class="k">module</span> <span class="nn">ApplicationHelper</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">current_user</span>
</span></span><span class="line"><span class="cl">    <span class="n">session</span><span class="o">[</span><span class="ss">:current_user</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>Por fim, o scaffolding padrão do Phoenix já vem com Bootstrap, então fica bem mais bonito que o &ldquo;scaffold.css&rdquo; de 10 anos atrás que o Rails gera por padrão. Existem várias gems que fazem essa substituição, claro.</p>
<h3>Testes<span class="hx:absolute hx:-mt-20" id="testes"></span>
    <a href="#testes" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A última coisa é o sistema de testes. O Rails usa Minitest, o Elixir usa ExUnit. De novo, os helpers são tão parecidos que você consegue traduzir quase diretamente do Phoenix para o Rails e vice-versa.</p>
<p>Como ressalva, os testes do Rails evoluíram muito na última década. Mesmo sem adicionar features extras como o Factory Girl, as Fixtures padrão ainda não têm equivalente no Phoenix (#OpportunityToContribute!).</p>
<p>Vamos começar vendo um pequeno teste de modelo no Phoenix:</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"># test/models/post_test.ex</span>
</span></span><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">Pxblog</span><span class="o">.</span><span class="n">PostTest</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">Pxblog</span><span class="o">.</span><span class="n">ModelCase</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">Pxblog</span><span class="o">.</span><span class="n">Post</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="vi">@valid_attrs</span> <span class="sx">%{body: &#34;some content&#34;, title: &#34;some content&#34;}</span>
</span></span><span class="line"><span class="cl">  <span class="vi">@invalid_attrs</span> <span class="sx">%{}</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;changeset with valid attributes&#34;</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">Post</span><span class="o">.</span><span class="n">changeset</span><span class="p">(</span><span class="o">%</span><span class="no">Post</span><span class="p">{},</span> <span class="vi">@valid_attrs</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">assert</span> <span class="n">changeset</span><span class="o">.</span><span class="n">valid?</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="nb">test</span> <span class="s2">&#34;changeset with invalid attributes&#34;</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">Post</span><span class="o">.</span><span class="n">changeset</span><span class="p">(</span><span class="o">%</span><span class="no">Post</span><span class="p">{},</span> <span class="vi">@invalid_attrs</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">refute</span> <span class="n">changeset</span><span class="o">.</span><span class="n">valid?</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 os mesmos testes unitários em Rails:</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"># test/models/post_test.rb</span>
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;test_helper&#39;</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">PostTest</span> <span class="o">&lt;</span> <span class="no">ActiveSupport</span><span class="o">::</span><span class="no">TestCase</span>
</span></span><span class="line"><span class="cl">  <span class="n">setup</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="vi">@valid_attr</span> <span class="o">=</span> <span class="p">{</span><span class="ss">body</span><span class="p">:</span> <span class="s2">&#34;some content&#34;</span><span class="p">,</span> <span class="ss">title</span><span class="p">:</span> <span class="s2">&#34;some content&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="vi">@invalid_attr</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="nb">test</span> <span class="s2">&#34;with valid attributes&#34;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">post</span> <span class="o">=</span> <span class="no">Post</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="vi">@valid_attr</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">assert</span> <span class="n">post</span><span class="o">.</span><span class="n">valid?</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="nb">test</span> <span class="s2">&#34;with invalida attributes&#34;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">post</span> <span class="o">=</span> <span class="no">Post</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="vi">@invalid_attr</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">refute</span> <span class="n">post</span><span class="o">.</span><span class="n">valid?</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>Notavelmente parecidos. Para os dados de teste, no Phoenix estamos usando atributos de módulo simples do Elixir; no Rails colocamos no setup para criar variáveis de instância. Eles não são a mesma coisa, mas o resultado é parecido. De novo, no Rails testamos o modelo, no Phoenix testamos o changeset.</p>
<p>Agora vamos ver um pedaço de um teste de controller. Como falei antes, como o Phoenix não tem uma feature de Fixtures ou Factory disponível (apesar de o pessoal legal da Thoughtbot ter acabado de lançar uma biblioteca estilo Factory Girl para Phoenix chamada <a href="https://robots.thoughtbot.com/announcing-ex-machina"target="_blank" rel="noopener">ExMachina</a>), temos que fazer um pouco mais de setup:</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"># test/controllers/session_controller.ex</span>
</span></span><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">Pxblog</span><span class="o">.</span><span class="n">SessionControllerTest</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">Pxblog</span><span class="o">.</span><span class="n">ConnCase</span>
</span></span><span class="line"><span class="cl">  <span class="k">alias</span> <span class="no">Pxblog</span><span class="o">.</span><span class="n">User</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">setup</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="no">User</span><span class="o">.</span><span class="n">changeset</span><span class="p">(</span><span class="o">%</span><span class="no">User</span><span class="p">{},</span> <span class="sx">%{username: &#34;test&#34;, password: &#34;test&#34;, password_confirmation: &#34;test&#34;, email: &#34;test@test.com&#34;}</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Repo</span><span class="o">.</span><span class="n">insert</span>
</span></span><span class="line"><span class="cl">    <span class="n">conn</span> <span class="o">=</span> <span class="n">conn</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="ss">conn</span><span class="p">:</span> <span class="n">conn</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="nb">test</span> <span class="s2">&#34;shows the login form&#34;</span><span class="p">,</span> <span class="sx">%{conn: conn}</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">conn</span> <span class="o">=</span> <span class="n">get</span> <span class="n">conn</span><span class="p">,</span> <span class="n">session_path</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="ss">:new</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">assert</span> <span class="n">html_response</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="mi">200</span><span class="p">)</span> <span class="o">=~</span> <span class="s2">&#34;Login&#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="nb">test</span> <span class="s2">&#34;creates a new user session for a valid user&#34;</span><span class="p">,</span> <span class="sx">%{conn: conn}</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">conn</span> <span class="o">=</span> <span class="n">post</span> <span class="n">conn</span><span class="p">,</span> <span class="n">session_path</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="ss">:create</span><span class="p">),</span> <span class="ss">user</span><span class="p">:</span> <span class="sx">%{username: &#34;test&#34;, password: &#34;test&#34;}</span>
</span></span><span class="line"><span class="cl">    <span class="n">assert</span> <span class="n">get_session</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="ss">:current_user</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">assert</span> <span class="n">get_flash</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="ss">:info</span><span class="p">)</span> <span class="o">==</span> <span class="s2">&#34;Sign in successful!&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">assert</span> <span class="n">redirected_to</span><span class="p">(</span><span class="n">conn</span><span class="p">)</span> <span class="o">==</span> <span class="n">page_path</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="ss">:index</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>O teste de controller no Rails está pegando os dados de uma Fixture, por isso o setup é mais curto:</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"># test/controllers/session_controller.rb</span>
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;test_helper&#39;</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">SessionsControllerTest</span> <span class="o">&lt;</span> <span class="no">ActionController</span><span class="o">::</span><span class="no">TestCase</span>
</span></span><span class="line"><span class="cl">  <span class="n">setup</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="vi">@user</span> <span class="o">=</span> <span class="n">users</span><span class="p">(</span><span class="ss">:user_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></span><span class="line"><span class="cl">  <span class="nb">test</span> <span class="s2">&#34;shows the login form&#34;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">get</span> <span class="ss">:new</span>
</span></span><span class="line"><span class="cl">    <span class="n">assert_response</span> <span class="ss">:success</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="nb">test</span> <span class="s2">&#34;creates a new user session for a valid user&#34;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">post</span> <span class="ss">:create</span><span class="p">,</span> <span class="ss">user</span><span class="p">:</span> <span class="p">{</span><span class="ss">username</span><span class="p">:</span> <span class="vi">@user</span><span class="o">.</span><span class="n">username</span><span class="p">,</span> <span class="ss">password</span><span class="p">:</span> <span class="s2">&#34;password&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="n">assert</span> <span class="n">session</span><span class="o">[</span><span class="ss">:current_user</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">assert</span> <span class="n">flash</span><span class="o">[</span><span class="ss">:notice</span><span class="o">]</span> <span class="o">==</span> <span class="s2">&#34;Sign in successful!&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">assert_redirected_to</span> <span class="n">user_posts_path</span><span class="p">(</span><span class="vi">@user</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>Mas como falei antes, o Phoenix implementa helpers parecidos, então é bem direto portar de Rails para Phoenix aqui.</p>
<p>Você executa o test runner do Rails com <tt>rake test</tt> e o do Phoenix com <tt>mix test</tt>. Não tem muito o que ver aqui. Os testes carregam muito rápido, eu acredito que rodam em paralelo, e é bem rápido, o que é uma boa diferença em relação a quando você tem uma aplicação Rails grande de verdade, onde a suíte de testes pode levar minutos para terminar.</p>
<h3>Conclusões<span class="hx:absolute hx:-mt-20" id="conclusões"></span>
    <a href="#conclus%c3%b5es" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Se você chegou até aqui, talvez se interesse em seguir o tutorial do Brandon e depois construir sua própria cópia em Rails, ou pode simplesmente clonar dos meus repositórios no Github. <a href="https://github.com/akitaonrails/pxblog_phoenix_exercise"target="_blank" rel="noopener">Este é o exercício Phoenix</a> e <a href="https://github.com/akitaonrails/pxblog_rails_exercise"target="_blank" rel="noopener">este é o exercício Rails</a>.</p>
<p>O que dá para concluir até aqui é que o Phoenix já é um framework web bem completo. E isso sem nem tocar no que eu considero seu maior feito: o suporte notável a Websockets que recentemente foi testado e benchmarkado com máquinas de verdade da Digital Ocean, alcançando um pico de <a href="http://www.akitaonrails.com/2015/10/29/phoenix-experiment-holding-2-million-websocket-clients"target="_blank" rel="noopener"><strong>2 milhões de conexões concorrentes</strong></a>.</p>
<p>Eu acho que esse é o caso de uso real para o Phoenix: microserviços que entreguem a promessa do Erlang OTP de alta confiabilidade e alta concorrência. Para esse objetivo já está pronto para produção, e você pode usá-lo agora mesmo. Ainda há muito a se aprender sobre tuning adequado, monitoramento adequado, troubleshooting adequado em ambiente de produção, e por aí vai, mas está chegando lá.</p>
<p>Como substituto completo do Ruby on Rails, ainda não está no mesmo nível. Mas é injusto dizer isso, porque um framework novo não consegue competir com 10 anos de um ecossistema inteiro criando todo tipo de feature, ferramenta e técnica. O Rails é uma potência e vai continuar sendo.</p>
<p>Neste exemplo simplificado já notamos uma pequena feature que ainda não temos no Phoenix: o equivalente a <tt>ActiveSupport#has_secure_password</tt>. Mas isso não é tudo: ainda não temos os equivalentes para Devise, Simple Forms, Active Admin ou Rails Admin, Refile, Koala, Spree, etc. Mas podemos ter, à medida que o ecossistema for preenchendo as lacunas (#OpportunityToContribute!) desse novo ecossistema Phoenix. E eu encorajo as pessoas a fazerem isso, já que a Thoughtbot já entregou o ExMachina para quem gosta do Factory Girl entrar de cabeça.</p>
<p>Do jeito que está agora, parece a época em que o Rails 1.2 acabou de ser lançado: muitas bibliotecas eram jovens demais, ainda não tínhamos ferramentas maduras. Mas o trunfo do Phoenix e do Elixir é o núcleo Erlang, com décadas de uso e testado em batalha. Isso é algo que ninguém mais tem, e a melhor maneira de capitalizar isso é ir além do que ferramentas como Node.js conseguiram: um ambiente de programação realmente <strong>prazeroso</strong>, com uma linguagem que foi muito bem projetada e que mira na felicidade do programador, e ainda por cima com um núcleo muito maduro.</p>
<p>Programadores não deveriam ter que brigar por coisas pequenas. Gerenciamento de tarefas? Resolvido, use Mix. Gerenciamento de pacotes? Resolvido, use Hex. Promises? Não precisa. Semântica de require? Não precisa. Você entendeu a ideia.</p>
<p>Se você é programador Rails e quer encontrar uma plataforma companheira para incrementar suas soluções Rails existentes com microserviços de alta confiabilidade e alta concorrência, o Phoenix é uma opção pronta para produção agora mesmo. Pula dentro!</p>
]]></content:encoded><category>elixir-e-erlang</category><category>rails</category><category>desenvolvimento-web</category></item></channel></rss>