<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Desenvolvimento-Web | AkitaOnRails.com</title><link>https://akitaonrails.github.io/tags/desenvolvimento-web/</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/desenvolvimento-web/index.xml" rel="self" type="application/rss+xml"/><item><title>Frontend Sem Framework | Bastidores do The M.Akita Chronicles</title><link>https://akitaonrails.github.io/2026/02/19/frontend-sem-framework-bastidores-do-the-m-akita-chronicles/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/02/19/frontend-sem-framework-bastidores-do-the-m-akita-chronicles/</guid><pubDate>Thu, 19 Feb 2026 01:08:23 +0000</pubDate><description>&lt;p&gt;Este post vai fazer parte de uma série; acompanhe pela tag &lt;a href="https://akitaonrails.github.io/tags/the-m-akita-chronicles/"&gt;/themakitachronicles&lt;/a&gt;. Esta é a parte 4.&lt;/p&gt;
&lt;p&gt;E não deixe de assinar minha nova newsletter &lt;a href="https://themakitachronicles.com/"target="_blank" rel="noopener"&gt;The M.Akita Chronicles&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;&amp;ndash;&lt;/p&gt;
&lt;p&gt;Existe uma obsessão no mercado de que frontend = React. Ou Vue. Ou Svelte. Ou o framework da semana. E se eu te disser que é possível ter um frontend moderno, responsivo, com dark mode, email-compatible, multi-plataforma — sem uma única linha de JavaScript framework?&lt;/p&gt;</description><content:encoded><![CDATA[<p>Este post vai fazer parte de uma série; acompanhe pela tag <a href="/tags/the-m-akita-chronicles/">/themakitachronicles</a>. Esta é a parte 4.</p>
<p>E não deixe de assinar minha nova newsletter <a href="https://themakitachronicles.com/"target="_blank" rel="noopener">The M.Akita Chronicles</a>!</p>
<p>&ndash;</p>
<p>Existe uma obsessão no mercado de que frontend = React. Ou Vue. Ou Svelte. Ou o framework da semana. E se eu te disser que é possível ter um frontend moderno, responsivo, com dark mode, email-compatible, multi-plataforma — sem uma única linha de JavaScript framework?</p>
<p>Não estou falando de sites estáticos dos anos 2000. Estou falando de Tailwind CSS v4 no Rails, Hugo com tema Hextra pro blog, e templates de email que funcionam do Gmail ao Outlook 2016. Tudo no mesmo projeto, com um desenvolvedor.</p>
<h2>Tailwind CSS v4: O Fim do CSS Artesanal<span class="hx:absolute hx:-mt-20" id="tailwind-css-v4-o-fim-do-css-artesanal"></span>
    <a href="#tailwind-css-v4-o-fim-do-css-artesanal" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Eu tenho uma confissão: CSS sempre foi minha kryptonita. Não porque é difícil — mas porque é <strong>imprevisível</strong>. Você muda um <code>margin-top</code> e quebra o layout de uma página que nem tocou. Cascata, especificidade, herança — é um campo minado.</p>
<p>Tailwind resolve isso da forma mais pragmática possível: classes utilitárias direto no HTML.</p>
<div class="hextra-code-block 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">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;flex items-center gap-4 p-6 bg-white dark:bg-gray-800
</span></span></span><span class="line"><span class="cl"><span class="s">            rounded-xl shadow-sm border border-gray-200 dark:border-gray-700&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">img</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;w-12 h-12 rounded-full&#34;</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;/images/avatar.jpg&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">h3</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;font-semibold text-gray-900 dark:text-gray-100&#34;</span><span class="p">&gt;</span>Título<span class="p">&lt;/</span><span class="nt">h3</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="na">class</span><span class="o">=</span><span class="s">&#34;text-sm text-gray-500 dark:text-gray-400&#34;</span><span class="p">&gt;</span>Descrição<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">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-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;Mas isso é inline style com passos extras!&rdquo; — Não, não é. É um sistema de design com constraints. Você não escolhe &ldquo;margin: 17px&rdquo; — você escolhe <code>m-4</code> (16px) ou <code>m-5</code> (20px). Esse constraint force consistência visual sem precisar de um design system documentado em 40 páginas.</p>
<p>E o Tailwind v4, que veio com o Rails via <code>tailwindcss-rails</code>, tem uma mudança fundamental: <strong>CSS-first configuration</strong>. Acabou o <code>tailwind.config.js</code>. Tudo é CSS:</p>
<div class="hextra-code-block 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="p">@</span><span class="k">import</span> <span class="s2">&#34;tailwindcss&#34;</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="k">theme</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">--color-brand</span><span class="o">:</span> <span class="p">#</span><span class="nn">8b5cf6</span><span class="o">;</span>
</span></span><span class="line"><span class="cl">  <span class="nt">--font-sans</span><span class="o">:</span> <span class="s2">&#34;Inter&#34;</span><span class="o">,</span> <span class="nt">system-ui</span><span class="o">,</span> <span class="nt">sans-serif</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>Menos um arquivo de config. Menos um passo de build. Mais simples.</p>
<h2>Dark Mode: Respeite Seu Usuário<span class="hx:absolute hx:-mt-20" id="dark-mode-respeite-seu-usuário"></span>
    <a href="#dark-mode-respeite-seu-usu%c3%a1rio" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-20_01-05-42.jpg" alt="change theme"  loading="lazy" /></p>
<p>Uma coisa que me irrita profundamente é site que ignora preferência de tema do sistema operacional. O usuário configurou dark mode no OS — respeita.</p>
<p>Com Tailwind, dark mode é trivial:</p>
<div class="hextra-code-block 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">body</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100&#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>O <code>dark:</code> prefixo aplica o estilo quando o sistema operacional está em dark mode (via <code>prefers-color-scheme: dark</code>). Não precisa de JavaScript. Não precisa de toggle. Não precisa de cookie. Funciona.</p>
<p>Mas &ldquo;funciona&rdquo; é o mínimo. O desafio real é manter <strong>consistência</strong> entre dezenas de componentes. Toda cor que você usar precisa de uma variante <code>dark:</code>. Todo background, todo texto, toda borda.</p>
<p>O truque: crie convenções e siga. Background principal? <code>bg-white dark:bg-gray-900</code>. Texto primário? <code>text-gray-900 dark:text-gray-100</code>. Texto secundário? <code>text-gray-500 dark:text-gray-400</code>. Bordas? <code>border-gray-200 dark:border-gray-700</code>. Com 4-5 pares definidos, 90% dos componentes ficam cobertos.</p>
<h2>Hugo + Hextra: Blog Sem Banco de Dados<span class="hx:absolute hx:-mt-20" id="hugo--hextra-blog-sem-banco-de-dados"></span>
    <a href="#hugo--hextra-blog-sem-banco-de-dados" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O blog do projeto roda Hugo com o tema Hextra. Hugo é um gerador de sites estáticos escrito em Go — absurdamente rápido. Rebuild completo em &lt;500ms mesmo com dezenas de páginas.</p>
<p>O que torna o Hextra especial é que ele é um tema de <strong>documentação</strong> adaptado pra blog. Sidebar de navegação, search integrado, table of contents — tudo pronto. E customizável via layouts parciais:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>layouts/
├── _partials/
│   ├── custom/
│   │   └── head-end.html          # Injeta analytics, fonts, CSS customizado
│   └── section-box.html           # Partial compartilhado (DRY, veja abaixo)
├── blog/
│   └── home.html                  # Homepage customizada
├── newsletters/
│   └── single.html                # Template de newsletter individual
├── podcast-transcripts/
│   └── list.html                  # Listagem de episódios
└── shortcodes/                    # 17 shortcodes — componentes sem JS
    ├── podcast-turn.html          # Balão de fala no transcript
    ├── akita-comment.html         # Comentário do Akita
    ├── marvin-comment.html        # Comentário do Marvin
    ├── history-timeline.html      # Timeline de eventos históricos
    ├── market-ticker.html         # Tabela de tickers financeiros
    ├── market-map.html            # Mapa FinViz S&amp;P 500
    ├── subscribe-cta.html         # Call-to-action de inscrição
    ├── score.html                 # Badge de relevância (high/medium/low)
    ├── holidays.html              # Feriados e datas comemorativas
    ├── ref-stories.html           # Links de referência
    ├── book-downloads.html        # Links de download do livro
    ├── hacker-news.html           # ┐
    ├── anime-ranking.html         # │ Section boxes — delegam para
    ├── youtube-digest.html        # │ section-box.html (ver abaixo)
    ├── world-events.html          # │
    ├── market-news.html           # │
    └── qa.html                    # ┘</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 tipo de conteúdo tem seu template. Newsletters têm sidebar de navegação e seções visuais. Transcrições de podcast têm player embutido e formatação de diálogo. O mesmo conteúdo Markdown é renderizado de formas diferentes dependendo do <code>type</code> no frontmatter.</p>
<h2>Shortcodes: Componentes Sem JavaScript<span class="hx:absolute hx:-mt-20" id="shortcodes-componentes-sem-javascript"></span>
    <a href="#shortcodes-componentes-sem-javascript" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Hugo shortcodes são o equivalente a componentes React, mas sem build step:</p>
<div class="hextra-code-block 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;!-- layouts/shortcodes/podcast-turn.html --&gt;</span>
</span></span><span class="line"><span class="cl">{{ $speaker := .Get &#34;speaker&#34; }}
</span></span><span class="line"><span class="cl">{{ $isAkita := eq $speaker &#34;akita&#34; }}
</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;podcast-turn&#34;</span> <span class="na">style</span><span class="o">=</span><span class="s">&#34;display: flex; {{ if $isAkita }}flex-direction: row{{ else }}flex-direction: row-reverse{{ end }};&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">img</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;/images/{{ $speaker }}.jpg&#34;</span>
</span></span><span class="line"><span class="cl">       <span class="na">style</span><span class="o">=</span><span class="s">&#34;width: 40px; height: 40px; border-radius: 50%;&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;bubble {{ if $isAkita }}akita-bubble{{ else }}marvin-bubble{{ end }}&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    {{ .Inner | markdownify }}
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-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 Markdown, o autor escreve:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>{{&lt; podcast-turn speaker=&#34;akita&#34; &gt;}}
Então, vamos falar sobre o que aconteceu essa semana no mundo tech...
{{&lt; /podcast-turn &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><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-20_01-06-23.jpg" alt="baloon"  loading="lazy" /></p>
<p>E o Hugo renderiza um balão de chat estilizado, com avatar, alinhamento correto, e markdown processado. Zero JavaScript. Zero framework. HTML + CSS + template logic.</p>
<h3>DRY com Partial Compartilhado<span class="hx:absolute hx:-mt-20" id="dry-com-partial-compartilhado"></span>
    <a href="#dry-com-partial-compartilhado" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Com 17 shortcodes, duplicação vira problema rápido. Sete deles — os &ldquo;section boxes&rdquo; que envolvem seções da newsletter (Hacker News, Anime, YouTube, etc.) — têm a mesma estrutura visual: ícone, título, borda colorida, conteúdo Markdown. Só mudam a cor e o label.</p>
<p>A solução é um partial compartilhado que recebe parâmetros:</p>
<div class="hextra-code-block 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;!-- layouts/partials/section-box.html --&gt;</span>
</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;section-box {{ .class }}&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;section-box-header&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">span</span><span class="p">&gt;</span>{{ .icon }}<span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;</span> {{ .label }}
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;section-box-content&#34;</span><span class="p">&gt;</span>{{ .content | markdownify }}<span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-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 cada shortcode vira uma 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-html" data-lang="html"><span class="line"><span class="cl"><span class="c">&lt;!-- layouts/shortcodes/hacker-news.html --&gt;</span>
</span></span><span class="line"><span class="cl">{{ partial &#34;section-box&#34; (dict &#34;class&#34; &#34;hacker-news&#34; &#34;icon&#34; &#34;<span class="ni">&amp;#128274;</span>&#34; &#34;label&#34; &#34;The Hacker News&#34; &#34;content&#34; .Inner) }}</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-20_01-07-02.jpg" alt="hacker news"  loading="lazy" /></p>
<p>É o equivalente Hugo de herança de componentes: um partial polimórfico parametrizado por <code>dict</code>. O CSS em <code>custom.css</code> define cores por classe (<code>.hacker-news</code>, <code>.anime-ranking</code>, etc.) com variantes light/dark. Resultado: 7 shortcodes que antes tinham HTML duplicado agora são 7 linhas + 1 partial + CSS.</p>
<p>Mas tem um pegadinho crucial: <strong>shortcodes são exclusivos do Hugo</strong>. O mesmo conteúdo que vai pro blog também vai por email. E email não entende shortcodes.</p>
<h2>O Desafio: Mesmo Conteúdo, Dois Renders<span class="hx:absolute hx:-mt-20" id="o-desafio-mesmo-conteúdo-dois-renders"></span>
    <a href="#o-desafio-mesmo-conte%c3%bado-dois-renders" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Aqui está o problema central de frontend nesse tipo de projeto: o mesmo arquivo Markdown precisa funcionar em dois contextos completamente diferentes.</p>
<p><strong>No blog (Hugo):</strong></p>
<ul>
<li>Shortcodes funcionam (<code>\{\{&lt; subscribe-cta &gt;\}\}</code>)</li>
<li>CSS com classes (Tailwind, Hextra)</li>
<li>Dark mode via <code>prefers-color-scheme</code></li>
<li>Imagens responsive</li>
<li>Layout flexível</li>
</ul>
<p><strong>No email (Rails → Amazon SES):</strong></p>
<ul>
<li>Shortcodes são texto morto — precisam ser removidos</li>
<li>CSS deve ser <strong>inline</strong> (muitos clientes de email ignoram <code>&lt;style&gt;</code>)</li>
<li>Dark mode? Esqueça pra 80% dos clientes</li>
<li>Imagens com URLs absolutas e tamanho fixo</li>
<li>Layout com <code>&lt;table&gt;</code> (sim, em 2026)</li>
</ul>
<p>A solução: dois pipelines de renderização independentes. O conteúdo Markdown é a source of truth. O Hugo transforma pra web. O Rails transforma pra email. Cada um com suas regras.</p>
<p>No lado do Rails, o serviço <code>MarkdownToHtml</code> precisa:</p>
<ol>
<li>Processar Markdown padrão</li>
<li>Converter <code>\{\{&lt; shortcode &gt;\}\}</code> em HTML equivalente ou removê-los — via um hash de <code>SHORTCODE_PATTERNS</code> que mapeia cada shortcode a uma regex + template inline com CSS</li>
<li>Aplicar tema (light/dark baseado na preferência do assinante)</li>
<li>Injetar CSS inline pra compatibilidade de email clients</li>
<li>Reescrever URLs de imagem pra absolutas</li>
</ol>
<p>É mais trabalho que parece. Cada shortcode precisa de um equivalente email-safe. Avatares de comentário precisam de URLs absolutas. Seções com background colorido precisam de <code>&lt;td style=&quot;background-color:...&quot;&gt;</code> ao invés de classes CSS.</p>
<h2>Email HTML: O Pior Frontend do Mundo<span class="hx:absolute hx:-mt-20" id="email-html-o-pior-frontend-do-mundo"></span>
    <a href="#email-html-o-pior-frontend-do-mundo" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Se você acha que cross-browser compatibility é difícil, tente cross-email-client compatibility. O Outlook 2016 renderiza HTML usando o <strong>engine do Microsoft Word</strong>. Sim, Word. Não é piada.</p>
<p>As regras de sobrevivência:</p>
<ol>
<li><strong>Layout com <code>&lt;table&gt;</code></strong>. <code>display: flex</code>? <code>grid</code>? Não existem no Outlook.</li>
<li><strong>CSS inline em todo elemento</strong>. <code>&lt;style&gt;</code> tags são ignoradas por vários clients.</li>
<li><strong>Sem <code>margin</code> em imagens</strong>. Use <code>padding</code> na <code>&lt;td&gt;</code> pai.</li>
<li><strong>Fonts web não funcionam</strong>. Use font stack: <code>font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;</code></li>
<li><strong>Temas são preferência do assinante</strong>, não detecção automática — muitos email clients não suportam <code>prefers-color-scheme</code>.</li>
</ol>
<p>O template de email do projeto é um ERB que recebe variáveis de tema:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>&lt;td style=&#34;padding: 8px 28px; color: &lt;%= @theme[:text] %&gt;;
           background: &lt;%= @theme[:card_bg] %&gt;;&#34;&gt;
  &lt;%= raw @content %&gt;
&lt;/td&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>O tema define todas as cores:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<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">THEMES</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="ss">light</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="ss">text</span><span class="p">:</span> <span class="s2">&#34;#1a1a1a&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="ss">card_bg</span><span class="p">:</span> <span class="s2">&#34;#ffffff&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="ss">link_color</span><span class="p">:</span> <span class="s2">&#34;#7c3aed&#34;</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="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="ss">dark</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="ss">text</span><span class="p">:</span> <span class="s2">&#34;#e6edf3&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="ss">card_bg</span><span class="p">:</span> <span class="s2">&#34;#161b22&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="ss">link_color</span><span class="p">:</span> <span class="s2">&#34;#a78bfa&#34;</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="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>Cada assinante escolhe seu tema na hora de se inscrever. O email é renderizado com as cores corretas pra aquela pessoa. Sem media queries, sem detecção automática — <strong>porque email clients não suportam de forma confiável</strong>. É mais simples dar a escolha pro usuário.</p>
<h2>Paleta de Cores: HSL e a Ciência por Trás das Escolhas<span class="hx:absolute hx:-mt-20" id="paleta-de-cores-hsl-e-a-ciência-por-trás-das-escolhas"></span>
    <a href="#paleta-de-cores-hsl-e-a-ci%c3%aancia-por-tr%c3%a1s-das-escolhas" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>&ldquo;Mas como vocês escolheram essas cores?&rdquo; — Não foi no olhômetro. Existe uma ciência por trás, e ela se chama <strong>HSL</strong> (Hue, Saturation, Lightness).</p>
<p>A maioria dos desenvolvedores pensa em cor como hex (<code>#e8eef8</code>) ou RGB (<code>rgb(232, 238, 248)</code>). São formatos de máquina — úteis pra renderizar, inúteis pra <strong>projetar</strong>. Quando você olha <code>#3b50a2</code> e <code>#c93030</code> lado a lado, não tem como saber intuitivamente se vão funcionar juntas. São só números.</p>
<p>HSL resolve isso. Em vez de misturar canais de luz (Red, Green, Blue), você descreve cor em termos humanos:</p>
<ul>
<li><strong>Hue</strong> (matiz): a posição no círculo cromático, de 0° a 360°. Vermelho = 0°, verde = 120°, azul = 240°.</li>
<li><strong>Saturation</strong> (saturação): quão &ldquo;viva&rdquo; é a cor. 0% = cinza, 100% = cor pura.</li>
<li><strong>Lightness</strong> (luminosidade): quão clara ou escura. 0% = preto, 100% = branco.</li>
</ul>
<p>Com HSL, você controla cada dimensão independentemente. E é aí que entra a construção da paleta.</p>
<h3>Passo 1: Distribuir Matizes no Círculo Cromático<span class="hx:absolute hx:-mt-20" id="passo-1-distribuir-matizes-no-círculo-cromático"></span>
    <a href="#passo-1-distribuir-matizes-no-c%c3%adrculo-crom%c3%a1tico" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-20_01-09-47.jpg" alt="color wheel"  loading="lazy" /></p>
<p>A newsletter tem 8 tipos de seção, cada uma com cor própria. Pra que todas &ldquo;pertençam&rdquo; visualmente ao mesmo projeto, os matizes são distribuídos uniformemente no círculo de 360°:</p>
<table>
  <thead>
      <tr>
          <th>Seção</th>
          <th>Matiz</th>
          <th>Graus</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>YouTube</td>
          <td>Vermelho</td>
          <td>0°</td>
      </tr>
      <tr>
          <td>História</td>
          <td>Sépia</td>
          <td>35°</td>
      </tr>
      <tr>
          <td>Mundo</td>
          <td>Âmbar</td>
          <td>45°</td>
      </tr>
      <tr>
          <td>Livro/Downloads</td>
          <td>Verde</td>
          <td>140°</td>
      </tr>
      <tr>
          <td>Feriados</td>
          <td>Teal</td>
          <td>180°</td>
      </tr>
      <tr>
          <td>Anime/Q&amp;A</td>
          <td>Azul</td>
          <td>210°</td>
      </tr>
      <tr>
          <td>Market News</td>
          <td>Royal</td>
          <td>225°</td>
      </tr>
      <tr>
          <td>Hacker News</td>
          <td>Roxo</td>
          <td>270°</td>
      </tr>
  </tbody>
</table>
<p>Oito matizes espaçados ao redor do círculo. Não é aleatório — é a mesma lógica de uma <strong>paleta policromática</strong> em teoria das cores. Quando os matizes são equidistantes, o olho humano percebe harmonia mesmo com cores completamente diferentes. É o mesmo princípio que artistas usam com um círculo cromático de 12 cores desde o século XVIII.</p>
<h3>Passo 2: Fixar Saturação e Luminosidade por Camada<span class="hx:absolute hx:-mt-20" id="passo-2-fixar-saturação-e-luminosidade-por-camada"></span>
    <a href="#passo-2-fixar-satura%c3%a7%c3%a3o-e-luminosidade-por-camada" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/HSV_color_solid_cylinder_saturation_gray.jpg" alt="saturation luminosity"  loading="lazy" /></p>
<p>Aqui está o truque que faz tudo funcionar junto. Cada &ldquo;camada&rdquo; visual (background, texto, borda) tem saturação e luminosidade <strong>fixas</strong> — só o matiz muda:</p>
<p><strong>Tema claro:</strong></p>
<ul>
<li><strong>Backgrounds</strong>: ~92-95% luminosidade, saturação moderada. Com luminosidade tão alta, mesmo saturações diferentes resultam em cores pastel com peso visual parecido — nenhuma seção &ldquo;grita&rdquo; mais que outra.</li>
<li><strong>Texto</strong>: ~15-20% luminosidade. Escuro o suficiente pra garantir <strong>ratio de contraste &gt;7:1</strong> contra o background claro (acima do mínimo WCAG AAA de 7:1).</li>
<li><strong>Bordas</strong>: ~45-55% luminosidade. O acento cromático — visível mas não dominante.</li>
</ul>
<p><strong>Tema escuro:</strong></p>
<ul>
<li><strong>Backgrounds</strong>: ~10-12% luminosidade. Profundidade uniforme — todas as seções têm a mesma &ldquo;escuridão&rdquo;.</li>
<li><strong>Texto</strong>: ~80-85% luminosidade. Claro o suficiente pra <strong>ratio de contraste &gt;6:1</strong> no fundo escuro.</li>
<li><strong>Bordas</strong>: mesma faixa de luminosidade que no tema claro — funcionam como acento nos dois contextos.</li>
</ul>
<p>Na prática, isso significa que gerar a variante dark de qualquer cor é mecânico: mantém o matiz, inverte a luminosidade. Background que era 94% vira 11%. Texto que era 18% vira 83%. Não é design — é aritmética.</p>
<h3>Passo 3: Verificar Contraste (WCAG)<span class="hx:absolute hx:-mt-20" id="passo-3-verificar-contraste-wcag"></span>
    <a href="#passo-3-verificar-contraste-wcag" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><a href="https://accessibilityassistant.com/blog/accessibility-insights/wcag-2-colour-contrast-accessibility-guidelines/"target="_blank" rel="noopener"><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/Why-Colour-Contrast-Accessibility-Matters-More-Than-Ever.jpg" alt="Why Colour Contrast Accessibility Matters More Than Ever"  loading="lazy" /></a></p>
<p>Cores bonitas que ninguém consegue ler não servem pra nada. O padrão WCAG (Web Content Accessibility Guidelines) define ratios mínimos:</p>
<ul>
<li><strong>AA</strong>: 4.5:1 pra texto normal, 3:1 pra texto grande</li>
<li><strong>AAA</strong>: 7:1 pra texto normal, 4.5:1 pra texto grande</li>
</ul>
<p>Os backgrounds claros com ~95% luminosidade contra texto com ~20% luminosidade dão ratio &gt;7:1 — WCAG AAA. Os backgrounds escuros (~11%) contra texto claro (~82%) dão &gt;6:1 — confortavelmente WCAG AA, próximo de AAA.</p>
<p>Isso não é por altruísmo. Email com baixo contraste é mais difícil de ler, gera menos engajamento, e em casos extremos é sinalizado por filtros de acessibilidade de email clients.</p>
<h3>O Resultado<span class="hx:absolute hx:-mt-20" id="o-resultado"></span>
    <a href="#o-resultado" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-20_01-07-50.jpg" alt="example colors"  loading="lazy" /></p>
<p>Quando você abre a newsletter e vê a seção do Hacker News (roxa), seguida pelo YouTube (vermelha), seguida pelo Anime (azul) — elas parecem &ldquo;do mesmo projeto&rdquo; apesar de cores completamente diferentes. Isso é o HSL trabalhando: matizes diferentes, mesma saturação, mesma luminosidade. O olho percebe coerência sem saber explicar por quê.</p>
<p>E a melhor parte: adicionar uma seção nova é trivial. Escolhe um matiz que não está ocupado no círculo, aplica as mesmas regras de saturação/luminosidade por camada, e a nova seção automaticamente &ldquo;pertence&rdquo; ao design existente.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<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"># O comentário no código resume as regras:</span>
</span></span><span class="line"><span class="cl"><span class="c1"># - Hues evenly spaced: blue 210, green 140, sepia 35, amber 45, royal 225, purple 270, red 0, teal 180</span>
</span></span><span class="line"><span class="cl"><span class="c1"># - Light backgrounds: ~95% lightness, ~25% saturation</span>
</span></span><span class="line"><span class="cl"><span class="c1"># - Light text: ~20% lightness (&gt;7:1 contrast ratio on light bg)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># - Dark backgrounds: ~10-12% lightness</span>
</span></span><span class="line"><span class="cl"><span class="c1"># - Dark text: ~80-85% lightness (&gt;6:1 contrast ratio on dark bg)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># - Borders: ~45-55% lightness</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Seis regras. Oito matizes. Dois temas. 100% determinístico. Nenhuma decisão subjetiva de &ldquo;acho que esse azul fica bonito&rdquo; — é sistema de design derivado de propriedades geométricas do espaço de cor HSL.</p>
<h2>HTML Bem Formado: O Filtro de Spam que Ninguém Te Conta<span class="hx:absolute hx:-mt-20" id="html-bem-formado-o-filtro-de-spam-que-ninguém-te-conta"></span>
    <a href="#html-bem-formado-o-filtro-de-spam-que-ningu%c3%a9m-te-conta" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Tudo que descrevi acima — tables, CSS inline, font stacks — resolve o problema de <strong>renderização</strong>. Mas tem um segundo motivo pra seguir essas regras que a maioria dos desenvolvedores ignora: <strong>provedores de email analisam a qualidade do seu HTML pra decidir se é spam</strong>.</p>
<p>Gmail, Outlook, Yahoo — todos passam o HTML do seu email por heurísticas antes de mostrar pro usuário. HTML malformado é um dos sinais clássicos de spam. Faz sentido: spammers não testam templates. Eles cospem HTML quebrado gerado por ferramentas de mass-mailing baratas. Se seu email parece output de spammer, vai ser tratado como spam.</p>
<p>Os sinais que disparam filtros:</p>
<ol>
<li><strong>Tags não fechadas ou mal aninhadas</strong>. Um <code>&lt;div&gt;</code> que abre e nunca fecha, ou um <code>&lt;/td&gt;</code> antes do <code>&lt;/tr&gt;</code>, é flag imediata. Provedores esperam HTML que parse sem erros — não HTML &ldquo;que o browser conserta&rdquo;.</li>
<li><strong>Ratio imagem/texto desbalanceado</strong>. Email que é basicamente uma imagem gigante com pouco texto? Spam clássico. Provedores querem ver texto real — não texto dentro de imagens. A regra de ouro é manter pelo menos 60% texto, 40% imagem.</li>
<li><strong>Atributo <code>alt</code> ausente em imagens</strong>. Além de acessibilidade, <code>alt</code> vazio ou ausente é sinal de descuido — e descuido correlaciona com spam. Toda <code>&lt;img&gt;</code> precisa de <code>alt</code> descritivo.</li>
<li><strong>CSS que esconde conteúdo</strong>. <code>display: none</code>, <code>visibility: hidden</code>, <code>font-size: 0</code>, texto da mesma cor que o background — são técnicas clássicas de keyword stuffing. Mesmo que você use legitimamente pra responsive email, o filtro não sabe a diferença.</li>
<li><strong>HTML excessivamente pesado</strong>. Email com 500KB de HTML levanta suspeita. Mantenha leve — o conteúdo real raramente precisa de mais de 100KB de HTML.</li>
<li><strong>Links com texto que não corresponde ao href</strong>. <code>&lt;a href=&quot;https://site-real.com&quot;&gt;https://site-falso.com&lt;/a&gt;</code> é phishing textbook. Mesmo discrepâncias sutis (domínio no texto diferente do domínio no link) são penalizadas.</li>
</ol>
<p>O ponto que conecta tudo: <strong>as mesmas práticas que fazem email renderizar corretamente no Outlook são as que mantêm seus emails fora do spam</strong>. Tables com estrutura limpa? HTML bem formado. CSS inline em cada elemento ao invés de hacks com <code>&lt;style&gt;</code>? Sem CSS escondido. Imagens com dimensões explícitas e <code>alt</code> text? Conteúdo legítimo.</p>
<p>Não é coincidência. Provedores de email calibraram seus filtros observando o que spammers fazem — e spammers fazem exatamente o oposto de HTML email bem construído. Quando você segue as melhores práticas de compatibilidade, <strong>de graça</strong> você também está sinalizando legitimidade pros filtros de spam.</p>
<p>A lição prática: trate o HTML do seu email como se fosse um documento XML. Valide, teste, e garanta que todo tag abre e fecha na ordem certa. Use ferramentas como <a href="https://www.emailonacid.com/"target="_blank" rel="noopener">Email on Acid</a> ou <a href="https://www.litmus.com/"target="_blank" rel="noopener">Litmus</a> pra testar em múltiplos clients — eles também reportam problemas de spam scoring. E resista à tentação de copiar templates prontos da internet sem validar — muitos são um desastre de HTML malformado que passa despercebido no Gmail mas faz o Outlook ter um ataque cardíaco e faz o filtro de spam do Yahoo te marcar como suspeito.</p>
<h2>RSS Feeds: O Frontend Esquecido<span class="hx:absolute hx:-mt-20" id="rss-feeds-o-frontend-esquecido"></span>
    <a href="#rss-feeds-o-frontend-esquecido" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-20_01-12-48.jpg" alt="RSS"  loading="lazy" /></p>
<p>Um detalhe que desenvolvedores web ignoram: RSS é um frontend. Podcasts no Spotify e Apple Podcasts são consumidos via RSS. O feed precisa de:</p>
<ul>
<li>XML válido (parsers de podcast são rígidos)</li>
<li>Metadata completa (título, descrição, duração, tamanho do arquivo)</li>
<li>URLs de imagem que <strong>realmente funcionam</strong> (Spotify valida)</li>
<li>Cover art com dimensões mínimas (1400×1400 pra Spotify)</li>
<li>GUIDs únicos por episódio</li>
</ul>
<p>Hugo gera RSS automaticamente, mas o template padrão não tem os campos que plataformas de podcast exigem. Um template customizado com tags iTunes é necessário:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;itunes:duration&gt;</span>{{ .Params.duration_seconds }}<span class="nt">&lt;/itunes:duration&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;itunes:image</span> <span class="na">href=</span><span class="s">&#34;{{ .Params.cover_image | absURL }}&#34;</span><span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;enclosure</span> <span class="na">url=</span><span class="s">&#34;{{ .Params.audio_url }}&#34;</span>
</span></span><span class="line"><span class="cl">           <span class="na">length=</span><span class="s">&#34;{{ .Params.file_size }}&#34;</span>
</span></span><span class="line"><span class="cl">           <span class="na">type=</span><span class="s">&#34;audio/mpeg&#34;</span><span class="nt">/&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>
<h2>A Filosofia: Conteúdo Primeiro, Framework Nunca<span class="hx:absolute hx:-mt-20" id="a-filosofia-conteúdo-primeiro-framework-nunca"></span>
    <a href="#a-filosofia-conte%c3%bado-primeiro-framework-nunca" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O padrão que emerge de todo esse setup é: <strong>o conteúdo é soberano</strong>. Markdown com frontmatter YAML é o formato universal. Cada frontend (blog, email, RSS, podcast player) consome o mesmo conteúdo e renderiza do seu jeito.</p>
<p>Não existe &ldquo;componente React que busca dados de uma API&rdquo;. Existe um arquivo Markdown que é lido por quem precisar. Isso é radicalmente simples — e radicalmente poderoso. Trocar o blog engine de Hugo pra outro? Muda os templates, o conteúdo fica igual. Trocar o provedor de email de SES pra Sendgrid? Muda o mailer, os templates ficam iguais.</p>
<p>É o Unix philosophy aplicado a frontend: cada ferramenta faz uma coisa bem feita, conectadas por um formato universal (Markdown + YAML).</p>
<h2>Conclusão<span class="hx:absolute hx:-mt-20" id="conclusão"></span>
    <a href="#conclus%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Você não precisa de React pra ter frontend moderno. Tailwind v4 te dá design system com constraints, dark mode nativo, e zero JavaScript. Hugo te dá um blog absurdamente rápido com templates flexíveis. ERB com CSS inline te dá emails que funcionam em qualquer client.</p>
<p>O investimento real é entender as restrições de cada plataforma e construir pipelines de renderização pra cada uma. Não é aprender o framework da moda que vai morrer em 18 meses.</p>
<p>No final das contas, a melhor abstração de frontend é nenhuma. HTML, CSS, e Markdown. Simples, debugável, e vai funcionar daqui a 10 anos.</p>
]]></content:encoded><category>the-m-akita-chronicles</category><category>frontend</category><category>desenvolvimento-web</category></item><item><title>Web Scrapping em 2026 | Bastidores do The M.Akita Chronicles</title><link>https://akitaonrails.github.io/2026/02/18/web-scrapping-em-2026-bastidores-do-the-m-akita-chronicles/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/02/18/web-scrapping-em-2026-bastidores-do-the-m-akita-chronicles/</guid><pubDate>Wed, 18 Feb 2026 14:57:22 +0000</pubDate><description>&lt;p&gt;Este post vai fazer parte de uma série; acompanhe pela tag &lt;a href="https://akitaonrails.github.io/tags/the-m-akita-chronicles/"&gt;/themakitachronicles&lt;/a&gt;. Esta é a parte 2.&lt;/p&gt;
&lt;p&gt;E não deixe de assinar minha nova newsletter &lt;a href="https://themakitachronicles.com/"target="_blank" rel="noopener"&gt;The M.Akita Chronicles&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;&amp;ndash;&lt;/p&gt;
&lt;p&gt;Dez anos atrás, buscar dados em um site era trivial. Quatro linhas de Ruby: &lt;code&gt;Net::HTTP.get&lt;/code&gt;, Nokogiri parse, CSS selector, pronto. O mundo inteiro estava aberto pra qualquer script com um User-Agent decente.&lt;/p&gt;
&lt;p&gt;Hoje? Metade dos sites que preciso ler pra montar minha newsletter retorna &amp;ldquo;Are you a robot?&amp;rdquo; pra qualquer requisição HTTP convencional. A outra metade renderiza o conteúdo inteiramente em JavaScript — não há HTML nenhum na resposta do servidor. E os mais paranoicos combinam as duas coisas.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Este post vai fazer parte de uma série; acompanhe pela tag <a href="/tags/the-m-akita-chronicles/">/themakitachronicles</a>. Esta é a parte 2.</p>
<p>E não deixe de assinar minha nova newsletter <a href="https://themakitachronicles.com/"target="_blank" rel="noopener">The M.Akita Chronicles</a>!</p>
<p>&ndash;</p>
<p>Dez anos atrás, buscar dados em um site era trivial. Quatro linhas de Ruby: <code>Net::HTTP.get</code>, Nokogiri parse, CSS selector, pronto. O mundo inteiro estava aberto pra qualquer script com um User-Agent decente.</p>
<p>Hoje? Metade dos sites que preciso ler pra montar minha newsletter retorna &ldquo;Are you a robot?&rdquo; pra qualquer requisição HTTP convencional. A outra metade renderiza o conteúdo inteiramente em JavaScript — não há HTML nenhum na resposta do servidor. E os mais paranoicos combinam as duas coisas.</p>
<p>Meu objetivo era criar uma rotina diária pro M.arvin me devolver as principais notícias pela manhã no meu Discord. Assim eu não preciso sair caçando as notícias do dia. Parecia simples… no começo.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-18_11-59-42.jpg" alt="daily routine"  loading="lazy" /></p>
<p>Este post é sobre como passei de &ldquo;uma chamada HTTP&rdquo; pra &ldquo;empacotar um Chromium inteiro no Docker&rdquo; — e por que isso é a realidade de qualquer projeto que precisa ler a web programaticamente em 2026.</p>
<h2>A Idade de Ouro: HTTP Simples<span class="hx:absolute hx:-mt-20" id="a-idade-de-ouro-http-simples"></span>
    <a href="#a-idade-de-ouro-http-simples" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Vamos começar pelo que ainda funciona. Alguns serviços oferecem dados estruturados sem nenhuma resistê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="c1"># RSS feeds: o formato mais resiliente da web</span>
</span></span><span class="line"><span class="cl"><span class="n">uri</span> <span class="o">=</span> <span class="no">URI</span><span class="p">(</span><span class="s2">&#34;https://news.google.com/rss/search?q=when:24h+allinurl:reuters.com/world&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">xml</span> <span class="o">=</span> <span class="no">Net</span><span class="o">::</span><span class="no">HTTP</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">uri</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">doc</span> <span class="o">=</span> <span class="no">REXML</span><span class="o">::</span><span class="no">Document</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">xml</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">doc</span><span class="o">.</span><span class="n">get_elements</span><span class="p">(</span><span class="s2">&#34;//item&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">item</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="n">title</span> <span class="o">=</span> <span class="n">item</span><span class="o">.</span><span class="n">get_text</span><span class="p">(</span><span class="s2">&#34;title&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">to_s</span>
</span></span><span class="line"><span class="cl">  <span class="n">url</span>   <span class="o">=</span> <span class="n">item</span><span class="o">.</span><span class="n">get_text</span><span class="p">(</span><span class="s2">&#34;link&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">to_s</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"># APIs públicas: JSON limpo, documentado, estável</span>
</span></span><span class="line"><span class="cl"><span class="n">response</span> <span class="o">=</span> <span class="no">HTTParty</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;https://api.jikan.moe/v4/top/anime?filter=airing&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">animes</span> <span class="o">=</span> <span class="n">response</span><span class="o">.</span><span class="n">parsed_response</span><span class="o">[</span><span class="s2">&#34;data&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># GitHub trending: HTML server-rendered sem proteção</span>
</span></span><span class="line"><span class="cl"><span class="n">html</span> <span class="o">=</span> <span class="no">HTTParty</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;https://github.com/trending&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">body</span>
</span></span><span class="line"><span class="cl"><span class="n">doc</span> <span class="o">=</span> <span class="no">Nokogiri</span><span class="o">::</span><span class="no">HTML</span><span class="p">(</span><span class="n">html</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">repos</span> <span class="o">=</span> <span class="n">doc</span><span class="o">.</span><span class="n">css</span><span class="p">(</span><span class="s2">&#34;article.Box-row&#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>Hacker News, GitHub Trending, APIs de anime, feeds RSS, Project Gutenberg — todos funcionam com um <code>GET</code> e um parser. Sem JavaScript, sem autenticação implícita, sem fingerprinting. Assim como a web deveria ser.</p>
<p>O problema é que esses são a exceção. Os sites que têm dados financeiros, notícias exclusivas ou conteúdo premium — exatamente os dados que mais importam — estão cada vez mais blindados.</p>
<h2>O Primeiro Obstáculo: SPAs e JavaScript Rendering<span class="hx:absolute hx:-mt-20" id="o-primeiro-obstáculo-spas-e-javascript-rendering"></span>
    <a href="#o-primeiro-obst%c3%a1culo-spas-e-javascript-rendering" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Quando o servidor retorna algo tipo:</p>
<div class="hextra-code-block 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">html</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">body</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">div</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;root&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">script</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;/app-3fa8c2.js&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">body</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">html</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>Seu <code>Net::HTTP.get</code> retorna exatamente isso: uma div vazia e um link pra um bundle de 2 MB. O conteúdo real — artigos, preços, listas — é montado no browser por React, Vue, Angular ou o framework da semana.</p>
<p>Pra um script, essa página é inútil. Pra extrair o conteúdo, você precisa executar o JavaScript. Isso significa rodar um browser real.</p>
<p>No mundo Ruby, a resposta é a gem <code>ferrum</code> — um cliente CDP (Chrome DevTools Protocol) que controla o Chromium programaticamente:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<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">browser</span> <span class="o">=</span> <span class="no">Ferrum</span><span class="o">::</span><span class="no">Browser</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="ss">headless</span><span class="p">:</span> <span class="s2">&#34;new&#34;</span><span class="p">,</span> <span class="ss">timeout</span><span class="p">:</span> <span class="mi">30</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">browser</span><span class="o">.</span><span class="n">go_to</span><span class="p">(</span><span class="s2">&#34;https://www.morningstar.com/markets&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">browser</span><span class="o">.</span><span class="n">network</span><span class="o">.</span><span class="n">wait_for_idle</span>  <span class="c1"># espera o JS terminar de carregar</span>
</span></span><span class="line"><span class="cl"><span class="n">html</span> <span class="o">=</span> <span class="n">browser</span><span class="o">.</span><span class="n">body</span>  <span class="c1"># agora sim o DOM está populado</span>
</span></span><span class="line"><span class="cl"><span class="n">browser</span><span class="o">.</span><span class="n">quit</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-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 <code>browser.body</code> retorna o HTML <strong>depois</strong> que o JavaScript executou. Mesmos dados que você vê no browser, agora acessíveis pro seu parser.</p>
<p>Mas aqui começa a dor de cabeça: você precisa de um binário do Chromium disponível no sistema. No Docker, isso significa adicionar ~300MB à imagem. No servidor, significa mais uma dependência pra gerenciar. E a latência de abrir um browser, navegar, esperar JS renderizar, e extrair o HTML — estamos falando de 3 a 10 segundos por página, ao invés dos 200ms de um HTTP GET.</p>
<p>O projeto usa isso como estratégia principal no <code>ArticleFetcher</code>: primeiro tenta com browser (pra pegar SPAs), depois faz um fallback HTTP simples (pra pegar metadata OpenGraph quando o browser falha). A maioria dos links que os leitores mandam no Discord são de sites que renderizam com JavaScript, então o browser-first compensa.</p>
<h2>O Segundo Obstáculo: Bot Detection<span class="hx:absolute hx:-mt-20" id="o-segundo-obstáculo-bot-detection"></span>
    <a href="#o-segundo-obst%c3%a1culo-bot-detection" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Abrir uma página com Chromium headless resolve o problema de JavaScript. Mas resolve 50% dos sites — não 100%. Porque existe uma indústria inteira dedicada a distinguir browsers reais de browsers automatizados.</p>
<h3>O Que o Headless Chrome Revela<span class="hx:absolute hx:-mt-20" id="o-que-o-headless-chrome-revela"></span>
    <a href="#o-que-o-headless-chrome-revela" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Chromium em modo headless é quase idêntico ao Chrome normal. Quase. As diferenças são sutis, mas suficientes pra detecção:</p>
<p><strong>1. <code>navigator.webdriver</code></strong> — Em todo Chrome controlado por automação, essa propriedade retorna <code>true</code>. Em browsers normais, <code>undefined</code>. É o flag mais óbvio.</p>
<p><strong>2. <code>navigator.plugins</code></strong> — Chrome normal tem 3-5 plugins (PDF viewer, etc.). Headless retorna array vazio. Bot detectado.</p>
<p><strong>3. <code>navigator.userAgentData</code></strong> — A Client Hints API. Chrome real retorna brands, platform, mobile flag. Headless pode não ter isso.</p>
<p><strong>4. WebGL fingerprint</strong> — O renderer WebGL num headless Chrome retorna &ldquo;SwiftShader&rdquo; (renderizador por software). Chrome real retorna &ldquo;NVIDIA GeForce&rdquo; ou &ldquo;Intel Iris&rdquo; ou o que for. Bot detectado.</p>
<p><strong>5. Timing e behavior</strong> — Browsers reais demoram pra carregar. Fazem scroll. Movem o mouse. Têm focus events. Um script que faz <code>go_to</code> → <code>body</code> em 2 segundos não se parece com um humano.</p>
<p>A solução é o que chamo de StealthBrowser — um wrapper que injeta patches JavaScript antes de qualquer script da página rodar:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<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">StealthBrowser</span>
</span></span><span class="line"><span class="cl">  <span class="no">STEALTH_JS</span> <span class="o">=</span> <span class="o">&lt;&lt;~</span><span class="no">JS</span>
</span></span><span class="line"><span class="cl">    <span class="sr">//</span> <span class="no">Remove</span> <span class="n">o</span> <span class="n">flag</span> <span class="n">de</span> <span class="n">automação</span>
</span></span><span class="line"><span class="cl">    <span class="no">Object</span><span class="o">.</span><span class="n">defineProperty</span><span class="p">(</span><span class="n">navigator</span><span class="p">,</span> <span class="s1">&#39;webdriver&#39;</span><span class="p">,</span> <span class="p">{</span> <span class="ss">get</span><span class="p">:</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="kp">false</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="sr">//</span> <span class="no">Simula</span> <span class="n">plugins</span> <span class="n">reais</span>
</span></span><span class="line"><span class="cl">    <span class="no">Object</span><span class="o">.</span><span class="n">defineProperty</span><span class="p">(</span><span class="n">navigator</span><span class="p">,</span> <span class="s1">&#39;plugins&#39;</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="ss">get</span><span class="p">:</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="o">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span> <span class="nb">name</span><span class="p">:</span> <span class="s1">&#39;Chrome PDF Plugin&#39;</span><span class="p">,</span> <span class="ss">filename</span><span class="p">:</span> <span class="s1">&#39;internal-pdf-viewer&#39;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span> <span class="nb">name</span><span class="p">:</span> <span class="s1">&#39;Chrome PDF Viewer&#39;</span><span class="p">,</span> <span class="ss">filename</span><span class="p">:</span> <span class="s1">&#39;mhjfbmdgcfjbbpaeojofohoefgiehjai&#39;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span> <span class="nb">name</span><span class="p">:</span> <span class="s1">&#39;Native Client&#39;</span><span class="p">,</span> <span class="ss">filename</span><span class="p">:</span> <span class="s1">&#39;internal-nacl-plugin&#39;</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></span><span class="line"><span class="cl">    <span class="sr">//</span> <span class="no">Simula</span> <span class="no">WebGL</span> <span class="n">renderer</span> <span class="n">real</span>
</span></span><span class="line"><span class="cl">    <span class="n">const</span> <span class="n">getParameter</span> <span class="o">=</span> <span class="no">WebGLRenderingContext</span><span class="o">.</span><span class="n">prototype</span><span class="o">.</span><span class="n">getParameter</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="no">WebGLRenderingContext</span><span class="o">.</span><span class="n">prototype</span><span class="o">.</span><span class="n">getParameter</span> <span class="o">=</span> <span class="n">function</span><span class="p">(</span><span class="nb">p</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="nb">p</span> <span class="o">===</span> <span class="mi">37445</span><span class="p">)</span> <span class="k">return</span> <span class="s1">&#39;Google Inc. (NVIDIA)&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="k">if</span> <span class="p">(</span><span class="nb">p</span> <span class="o">===</span> <span class="mi">37446</span><span class="p">)</span> <span class="k">return</span> <span class="s1">&#39;ANGLE (NVIDIA, GeForce GTX 1080 ...)&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="k">return</span> <span class="n">getParameter</span><span class="o">.</span><span class="n">call</span><span class="p">(</span><span class="n">this</span><span class="p">,</span> <span class="nb">p</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="sr">//</span> <span class="no">Client</span> <span class="no">Hints</span> <span class="no">API</span> <span class="p">(</span><span class="n">ausente</span> <span class="n">em</span> <span class="n">headless</span> <span class="o">=</span> <span class="n">bot</span> <span class="n">signal</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">navigator</span><span class="o">.</span><span class="n">userAgentData</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="no">Object</span><span class="o">.</span><span class="n">defineProperty</span><span class="p">(</span><span class="n">navigator</span><span class="p">,</span> <span class="s1">&#39;userAgentData&#39;</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="ss">get</span><span class="p">:</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">({</span>
</span></span><span class="line"><span class="cl">          <span class="ss">brands</span><span class="p">:</span> <span class="o">[</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span> <span class="ss">brand</span><span class="p">:</span> <span class="s1">&#39;Chromium&#39;</span><span class="p">,</span> <span class="ss">version</span><span class="p">:</span> <span class="s1">&#39;131&#39;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span> <span class="ss">brand</span><span class="p">:</span> <span class="s1">&#39;Google Chrome&#39;</span><span class="p">,</span> <span class="ss">version</span><span class="p">:</span> <span class="s1">&#39;131&#39;</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="ss">mobile</span><span class="p">:</span> <span class="kp">false</span><span class="p">,</span> <span class="ss">platform</span><span class="p">:</span> <span class="s1">&#39;Linux&#39;</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="no">JS</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">fetch</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="nb">name</span><span class="p">:,</span> <span class="ss">wait_for</span><span class="p">:</span> <span class="kp">nil</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">browser</span> <span class="o">=</span> <span class="no">Ferrum</span><span class="o">::</span><span class="no">Browser</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="ss">headless</span><span class="p">:</span> <span class="s2">&#34;new&#34;</span><span class="p">,</span> <span class="ss">timeout</span><span class="p">:</span> <span class="mi">30</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="ss">browser_options</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;disable-blink-features&#34;</span> <span class="o">=&gt;</span> <span class="s2">&#34;AutomationControlled&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;user-agent&#34;</span> <span class="o">=&gt;</span> <span class="no">REAL_USER_AGENT</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"># Injeta ANTES de qualquer script da página rodar</span>
</span></span><span class="line"><span class="cl">    <span class="n">browser</span><span class="o">.</span><span class="n">page</span><span class="o">.</span><span class="n">command</span><span class="p">(</span><span class="s2">&#34;Page.addScriptToEvaluateOnNewDocument&#34;</span><span class="p">,</span> <span class="ss">source</span><span class="p">:</span> <span class="no">STEALTH_JS</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">browser</span><span class="o">.</span><span class="n">go_to</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">yield</span> <span class="n">browser</span>
</span></span><span class="line"><span class="cl">  <span class="k">ensure</span>
</span></span><span class="line"><span class="cl">    <span class="n">browser</span><span class="o">&amp;.</span><span class="n">quit</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 truque-chave é <code>Page.addScriptToEvaluateOnNewDocument</code> — um comando CDP que registra o JavaScript pra ser executado <strong>antes</strong> de qualquer script da página. Isso significa que, quando o bot detector roda, ele vê os valores falsos que injetamos, e não os reais. E <code>disable-blink-features=AutomationControlled</code> remove o flag <code>navigator.webdriver</code> no nível do browser, antes mesmo do JavaScript.</p>
<p>Isso funciona pra maioria dos sites. Morningstar, Hindenburg Research, sites de notícias genéricos — passam com o stealth. Yahoo Finance precisa de mais — TLS fingerprinting que bloqueia até o Chromium sem crumb authentication. Mas o browser stealth resolve o grosso.</p>
<h2>O Terceiro Obstáculo: DataDome e CDPs de Anti-Bot<span class="hx:absolute hx:-mt-20" id="o-terceiro-obstáculo-datadome-e-cdps-de-anti-bot"></span>
    <a href="#o-terceiro-obst%c3%a1culo-datadome-e-cdps-de-anti-bot" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>E aí chegamos aos profissionais. <strong>DataDome</strong>, Cloudflare Turnstile, PerimeterX, Akamai Bot Manager — são serviços especializados em detectar automação. E eles jogam num nível completamente diferente.</p>
<p>A Reuters, por exemplo, usa DataDome. Quando tentei acessar <code>reuters.com/world/</code> com o StealthBrowser, recebi de volta uma página de 1.500 bytes com um captcha JavaScript. Todos os patches de <code>navigator.webdriver</code>, WebGL, plugins — inúteis.</p>
<p>Por quê? Porque o DataDome não checa só propriedades JavaScript. Ele detecta o <strong>próprio protocolo de automação</strong>.</p>
<h3>O Problema Fundamental do CDP<span class="hx:absolute hx:-mt-20" id="o-problema-fundamental-do-cdp"></span>
    <a href="#o-problema-fundamental-do-cdp" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Quando o <strong>Ferrum</strong> (ou Puppeteer, ou Playwright) controla um Chrome, ele faz isso via Chrome DevTools Protocol — o CDP. E a primeira coisa que o Ferrum faz ao conectar é enviar <code>Runtime.enable</code> — um comando que ativa o runtime evaluation no browser.</p>
<p>O problema é que <code>Runtime.enable</code> muda o comportamento observável do browser. Especificamente, ele ativa interceptors internos que podem ser detectados via <code>console.debug</code>. O DataDome usa isso: ele faz um <code>console.debug</code> com um getter que observa se o runtime está interceptando. Se está — e com CDP sempre está — bot detectado.</p>
<div class="hextra-code-block 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">// O que o DataDome faz (simplificado):
</span></span></span><span class="line"><span class="cl"><span class="kd">let</span> <span class="nx">detected</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">el</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s1">&#39;div&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nb">Object</span><span class="p">.</span><span class="nx">defineProperty</span><span class="p">(</span><span class="nx">el</span><span class="p">,</span> <span class="s1">&#39;id&#39;</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">get</span><span class="o">:</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span> <span class="nx">detected</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span> <span class="k">return</span> <span class="s1">&#39;test&#39;</span><span class="p">;</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">debug</span><span class="p">(</span><span class="nx">el</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="c1">// Se Runtime.enable está ativo, o getter é trigado
</span></span></span><span class="line"><span class="cl"><span class="c1">// Em um browser normal, nã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>Isso não é algo que você pode patchar com JavaScript. O <code>Runtime.enable</code> é mandado pelo Ferrum <strong>antes</strong> de qualquer script rodar — é parte do protocolo de automação. Pra resolver, você precisaria modificar o Ferrum pra não mandar esse comando, o que quebraria a maioria das funcionalidades da gem.</p>
<h3>As Alternativas (e Por Que Nenhuma é Ideal)<span class="hx:absolute hx:-mt-20" id="as-alternativas-e-por-que-nenhuma-é-ideal"></span>
    <a href="#as-alternativas-e-por-que-nenhuma-%c3%a9-ideal" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Pesquisei extensivamente. As opções:</p>
<ol>
<li>
<p><strong>nodriver (Python)</strong>: Usa Chrome via CDP sem <code>Runtime.enable</code>. Não tem equivalente em Ruby. E mesmo assim, está numa corrida de gato-e-rato com DataDome.</p>
</li>
<li>
<p><strong>curl-impersonate</strong>: Faz TLS fingerprinting idêntico ao Chrome sem abrir um browser. Bom pra sites com TLS detection, inútil pra SPAs.</p>
</li>
<li>
<p><strong>Scraping APIs</strong> (ScraperAPI, ZenRows): Terceirizam o problema. Funcionam, mas custam dinheiro e adicionam latência e dependência.</p>
</li>
<li>
<p><strong>Fork do Ferrum</strong>: Modificar a gem pra opcionalmente não mandar <code>Runtime.enable</code>. Viável tecnicamente, mas manutenção infinita.</p>
</li>
<li>
<p><strong>Google News RSS</strong>: O Google já leu a Reuters. O resultado tá num feed RSS público. Sem JavaScript, sem DataDome, sem browser.</p>
</li>
</ol>
<p>A opção 5 venceu. Pra Reuters, troquei toda a complexidade de headless browser por:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<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">ReutersClient</span>
</span></span><span class="line"><span class="cl">  <span class="no">RSS_URL</span> <span class="o">=</span> <span class="s2">&#34;https://news.google.com/rss/search?q=when:24h+allinurl:reuters.com/world&#34;</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">fetch_headlines</span><span class="p">(</span><span class="ss">limit</span><span class="p">:</span> <span class="mi">10</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">xml</span> <span class="o">=</span> <span class="no">Net</span><span class="o">::</span><span class="no">HTTP</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="no">URI</span><span class="p">(</span><span class="no">RSS_URL</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="n">doc</span> <span class="o">=</span> <span class="no">REXML</span><span class="o">::</span><span class="no">Document</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">xml</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">doc</span><span class="o">.</span><span class="n">get_elements</span><span class="p">(</span><span class="s2">&#34;//item&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">first</span><span class="p">(</span><span class="n">limit</span><span class="p">)</span><span class="o">.</span><span class="n">filter_map</span> <span class="k">do</span> <span class="o">|</span><span class="n">item</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">      <span class="n">title</span> <span class="o">=</span> <span class="n">item</span><span class="o">.</span><span class="n">get_text</span><span class="p">(</span><span class="s2">&#34;title&#34;</span><span class="p">)</span><span class="o">&amp;.</span><span class="n">to_s</span><span class="o">&amp;.</span><span class="n">strip</span>
</span></span><span class="line"><span class="cl">      <span class="n">url</span>   <span class="o">=</span> <span class="n">item</span><span class="o">.</span><span class="n">get_text</span><span class="p">(</span><span class="s2">&#34;link&#34;</span><span class="p">)</span><span class="o">&amp;.</span><span class="n">to_s</span><span class="o">&amp;.</span><span class="n">strip</span>
</span></span><span class="line"><span class="cl">      <span class="k">next</span> <span class="k">if</span> <span class="n">title</span><span class="o">.</span><span class="n">blank?</span> <span class="o">||</span> <span class="n">url</span><span class="o">.</span><span class="n">blank?</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span> <span class="ss">title</span><span class="p">:</span> <span class="n">title</span><span class="o">.</span><span class="n">sub</span><span class="p">(</span><span class="sr">/\s*-\s*Reuters\s*\z/</span><span class="p">,</span> <span class="s2">&#34;&#34;</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">category</span><span class="p">:</span> <span class="kp">nil</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">rescue</span> <span class="no">StandardError</span> <span class="o">=&gt;</span> <span class="n">e</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">warn</span><span class="p">(</span><span class="s2">&#34;ReutersClient failed: </span><span class="si">#{</span><span class="n">e</span><span class="o">.</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="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>Simples. Confiável. Zero dependências de browser. O link do Google News redireciona pro artigo original quando o leitor clica — funciona perfeitamente pra uma newsletter.</p>
<p>Nem todo problema de scraping precisa de solução de scraping. Às vezes o dado já está disponível num formato acessível — você só precisa achar.</p>
<h2>O Quarto Obstáculo: Sites que Mudam de Estrutura (Diariamente)<span class="hx:absolute hx:-mt-20" id="o-quarto-obstáculo-sites-que-mudam-de-estrutura-diariamente"></span>
    <a href="#o-quarto-obst%c3%a1culo-sites-que-mudam-de-estrutura-diariamente" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Mesmo quando o scraping funciona, a manutenção nunca para. O Morningstar é um caso didático — porque me fez errar <strong>duas vezes</strong> antes de chegar numa solução resiliente.</p>
<h3>Tentativa 1: Selecionar pela Hierarquia DOM<span class="hx:absolute hx:-mt-20" id="tentativa-1-selecionar-pela-hierarquia-dom"></span>
    <a href="#tentativa-1-selecionar-pela-hierarquia-dom" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Originalmente, os links de artigos estavam dentro de <code>&lt;h2&gt;</code> e <code>&lt;h3&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">h2</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;/markets/article-slug&#34;</span><span class="p">&gt;</span>Título do Artigo<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;&lt;/</span><span class="nt">h2</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>Um dia depois: zero resultados. O Morningstar mudou a estrutura pra flat links com classes CSS específicas — sem heading elements:</p>
<div class="hextra-code-block 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">a</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;mdc-basic-feed-item__mdc&#34;</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;/markets/article-slug&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">header</span><span class="p">&gt;&lt;</span><span class="nt">h3</span><span class="p">&gt;</span>Título do Artigo<span class="p">&lt;/</span><span class="nt">h3</span><span class="p">&gt;&lt;/</span><span class="nt">header</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;mdc-metadata__mdc&#34;</span><span class="p">&gt;</span>Autor · Data<span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">a</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>
<h3>Tentativa 2: Selecionar pela Classe CSS<span class="hx:absolute hx:-mt-20" id="tentativa-2-selecionar-pela-classe-css"></span>
    <a href="#tentativa-2-selecionar-pela-classe-css" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A correção óbvia: trocar hierarquia DOM por seletores CSS baseados na classe do componente:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<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">ARTICLE_CSS</span> <span class="o">=</span> <span class="s2">&#34;a.mdc-basic-feed-item__mdc, a.mdc-card--link__mdc&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">doc</span><span class="o">.</span><span class="n">css</span><span class="p">(</span><span class="no">ARTICLE_CSS</span><span class="p">)</span><span class="o">.</span><span class="n">each</span> <span class="p">{</span> <span class="o">|</span><span class="n">link</span><span class="o">|</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>Funcionou. Por exatamente um dia. Depois: zero resultados de novo. O Morningstar — como muitos sites modernos com frameworks CSS-in-JS — gera nomes de classe dinamicamente. O que era <code>mdc-basic-feed-item__mdc</code> virou outra coisa. E pode virar algo diferente amanhã.</p>
<p>Isso é cada vez mais comum. Sites que usam CSS Modules, styled-components, ou Tailwind com JIT geram hashes aleatórios nos nomes de classe como parte do build. A cada deploy do site, as classes mudam. Seu seletor hardcoded é uma bomba-relógio.</p>
<h3>Tentativa 3: Não Depender de Nenhum Nome de Classe<span class="hx:absolute hx:-mt-20" id="tentativa-3-não-depender-de-nenhum-nome-de-classe"></span>
    <a href="#tentativa-3-n%c3%a3o-depender-de-nenhum-nome-de-classe" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A solução final é inverter a lógica: ao invés de dizer ao parser <strong>quais</strong> classes procurar, deixar o parser <strong>descobrir</strong> quais links são artigos baseado em propriedades estruturais que não mudam:</p>
<ol>
<li><strong>Links de artigos têm URLs longas</strong>: <code>/news/why-stock-market-is-up-today-2026-02-17</code> vs links de navegação como <code>/markets</code> ou <code>/tools</code></li>
<li><strong>Links de artigos são agrupados</strong>: aparecem em listas, feeds, grids — múltiplos links com a mesma classe CSS</li>
<li><strong>Links de artigos têm títulos descritivos</strong>: textos longos, não labels curtas como &ldquo;Markets&rdquo; ou &ldquo;Tools&rdquo;</li>
</ol>
<p>O algoritmo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<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="nc">self</span><span class="o">.</span><span class="nf">parse_html</span><span class="p">(</span><span class="n">html</span><span class="p">,</span> <span class="n">limit</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">doc</span> <span class="o">=</span> <span class="no">Nokogiri</span><span class="o">::</span><span class="no">HTML</span><span class="p">(</span><span class="n">html</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># 1. Coleta todos os &lt;a&gt; internos com href, classe, e texto suficiente</span>
</span></span><span class="line"><span class="cl">  <span class="n">candidates</span> <span class="o">=</span> <span class="n">doc</span><span class="o">.</span><span class="n">css</span><span class="p">(</span><span class="s2">&#34;a[href]&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">filter_map</span> <span class="k">do</span> <span class="o">|</span><span class="n">link</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">    <span class="n">href</span> <span class="o">=</span> <span class="n">link</span><span class="o">[</span><span class="s2">&#34;href&#34;</span><span class="o">].</span><span class="n">to_s</span><span class="o">.</span><span class="n">strip</span>
</span></span><span class="line"><span class="cl">    <span class="n">css_class</span> <span class="o">=</span> <span class="n">link</span><span class="o">[</span><span class="s2">&#34;class&#34;</span><span class="o">].</span><span class="n">to_s</span><span class="o">.</span><span class="n">strip</span>
</span></span><span class="line"><span class="cl">    <span class="k">next</span> <span class="k">if</span> <span class="n">href</span><span class="o">.</span><span class="n">blank?</span> <span class="o">||</span> <span class="n">css_class</span><span class="o">.</span><span class="n">blank?</span>
</span></span><span class="line"><span class="cl">    <span class="k">next</span> <span class="k">unless</span> <span class="n">internal_href?</span><span class="p">(</span><span class="n">href</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">path</span> <span class="o">=</span> <span class="n">extract_path</span><span class="p">(</span><span class="n">href</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">segments</span> <span class="o">=</span> <span class="n">path</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;/&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">reject</span><span class="p">(</span><span class="o">&amp;</span><span class="ss">:empty?</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">next</span> <span class="k">if</span> <span class="n">segments</span><span class="o">.</span><span class="n">length</span> <span class="o">&lt;</span> <span class="mi">2</span>  <span class="c1"># links de nav têm 1 segmento: /markets</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">title</span> <span class="o">=</span> <span class="n">extract_title</span><span class="p">(</span><span class="n">link</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">next</span> <span class="k">if</span> <span class="n">title</span><span class="o">.</span><span class="n">blank?</span> <span class="o">||</span> <span class="n">title</span><span class="o">.</span><span class="n">length</span> <span class="o">&lt;</span> <span class="mi">10</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="ss">title</span><span class="p">:</span> <span class="n">title</span><span class="p">,</span> <span class="ss">url</span><span class="p">:</span> <span class="n">absolutize</span><span class="p">(</span><span class="n">href</span><span class="p">),</span> <span class="ss">css_class</span><span class="p">:</span> <span class="n">css_class</span><span class="p">,</span> <span class="ss">slug_length</span><span class="p">:</span> <span class="n">path</span><span class="o">.</span><span class="n">length</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"># 2. Agrupa por classe CSS</span>
</span></span><span class="line"><span class="cl">  <span class="n">groups</span> <span class="o">=</span> <span class="n">candidates</span><span class="o">.</span><span class="n">group_by</span> <span class="p">{</span> <span class="o">|</span><span class="n">c</span><span class="o">|</span> <span class="n">c</span><span class="o">[</span><span class="ss">:css_class</span><span class="o">]</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># 3. Filtra: mínimo 3 links no grupo (clusters de artigos, não links soltos)</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># 4. Seleciona o grupo com maior média de comprimento de slug</span>
</span></span><span class="line"><span class="cl">  <span class="n">best</span> <span class="o">=</span> <span class="n">groups</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">select</span> <span class="p">{</span> <span class="o">|</span><span class="n">_cls</span><span class="p">,</span> <span class="n">items</span><span class="o">|</span> <span class="n">items</span><span class="o">.</span><span class="n">length</span> <span class="o">&gt;=</span> <span class="mi">3</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">max_by</span> <span class="p">{</span> <span class="o">|</span><span class="n">_cls</span><span class="p">,</span> <span class="n">items</span><span class="o">|</span> <span class="n">items</span><span class="o">.</span><span class="n">sum</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="ss">:slug_length</span><span class="o">]</span> <span class="p">}</span><span class="o">.</span><span class="n">to_f</span> <span class="o">/</span> <span class="n">items</span><span class="o">.</span><span class="n">length</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="o">[]</span> <span class="k">unless</span> <span class="n">best</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">best</span><span class="o">[</span><span class="mi">1</span><span class="o">].</span><span class="n">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">c</span><span class="o">|</span> <span class="n">c</span><span class="o">.</span><span class="n">slice</span><span class="p">(</span><span class="ss">:title</span><span class="p">,</span> <span class="ss">:url</span><span class="p">)</span> <span class="p">}</span><span class="o">.</span><span class="n">uniq</span> <span class="p">{</span> <span class="o">|</span><span class="n">a</span><span class="o">|</span> <span class="n">a</span><span class="o">[</span><span class="ss">:url</span><span class="o">]</span> <span class="p">}</span><span class="o">.</span><span class="n">first</span><span class="p">(</span><span class="n">limit</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 salva essa abordagem: <strong>nenhum nome de classe é hardcoded</strong>. O Morningstar pode renomear todas as classes amanhã e o parser vai continuar funcionando — porque ele descobre o padrão de artigos pela estrutura, não pelo nome.</p>
<p>O que é estável num site:</p>
<ul>
<li><strong>URLs de artigos são sempre mais longas</strong> que URLs de navegação (isso é arquitetural, não cosmético)</li>
<li><strong>Artigos são sempre agrupados</strong> sob o mesmo componente (isso é UX, não implementação)</li>
<li><strong>Títulos de artigos são descritivos</strong> (isso é editorial, não técnico)</li>
</ul>
<p>O que <strong>não</strong> é estável:</p>
<ul>
<li>Nomes de classes CSS (mudam a cada build)</li>
<li>Hierarquia DOM (muda a cada redesign)</li>
<li>Atributos <code>data-*</code> (mudam conforme a equipe de frontend decide)</li>
</ul>
<p>Resumindo: não tente identificar elementos pelo nome — identifique pelo comportamento. Links de artigos se comportam diferente de links de navegação de formas mensuráveis (URL length, clustering, título), e essas propriedades são inerentes ao que o elemento É, não a como ele foi implementado.</p>
<h2>A Hierarquia de Confiabilidade<span class="hx:absolute hx:-mt-20" id="a-hierarquia-de-confiabilidade"></span>
    <a href="#a-hierarquia-de-confiabilidade" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Depois de horas quebrando a cabeça, ficou óbvia uma hierarquia de fontes de dados:</p>
<table>
  <thead>
      <tr>
          <th>Fonte</th>
          <th>Confiabilidade</th>
          <th>Latência</th>
          <th>Manutenção</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>API oficial (JSON)</td>
          <td>Alta</td>
          <td>~200ms</td>
          <td>Baixa</td>
      </tr>
      <tr>
          <td>RSS/Atom feed</td>
          <td>Alta</td>
          <td>~300ms</td>
          <td>Baixa</td>
      </tr>
      <tr>
          <td>HTML server-rendered</td>
          <td>Média</td>
          <td>~500ms</td>
          <td>Média</td>
      </tr>
      <tr>
          <td>SPA com headless browser</td>
          <td>Baixa-Média</td>
          <td>~5s</td>
          <td>Alta</td>
      </tr>
      <tr>
          <td>Site com anti-bot ativo</td>
          <td>Baixa</td>
          <td>~8s+</td>
          <td>Altíssima</td>
      </tr>
  </tbody>
</table>
<p>A regra: <strong>sempre prefira a fonte mais simples que funcione</strong>. Se o site tem RSS, use RSS. Se tem API, use a API. Só vá pro headless browser quando não há alternativa.</p>
<p>No projeto, a distribuição ficou assim:</p>
<p><strong>HTTP simples</strong> (Net::HTTP ou HTTParty):</p>
<ul>
<li>Google News RSS (Reuters world headlines)</li>
<li>Hacker News RSS (top stories)</li>
<li>HackerNoon RSS (top stories)</li>
<li>dev.to API (trending articles)</li>
<li>Jikan API (anime rankings)</li>
<li>YouTube Data API (vídeos)</li>
<li>Yahoo Finance news RSS</li>
</ul>
<p><strong>Headless browser</strong> (StealthBrowser + Ferrum):</p>
<ul>
<li>Morningstar (SPA + componentes dinâmicos)</li>
<li>Yahoo Finance charts (TLS fingerprinting + crumb auth)</li>
<li>FinViz market map (canvas rendering)</li>
<li>Artigos genéricos de leitores (qualquer URL pode ser SPA)</li>
<li>Hindenburg Research (JavaScript-rendered)</li>
</ul>
<p>Ou seja: <strong>a maioria das fontes de dados funciona sem browser</strong>. O browser é necessário pra meia dúzia de sites problemáticos — mas são esses sites que dão mais trabalho.</p>
<h2>O Custo Real<span class="hx:absolute hx:-mt-20" id="o-custo-real"></span>
    <a href="#o-custo-real" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Vamos ser práticos sobre o que &ldquo;empacotar um Chrome&rdquo; significa:</p>
<p><strong>Imagem Docker</strong>: +300MB. O Chromium e suas dependências (fontes, libs gráficas, sandbox) são enormes. Num Dockerfile otimizado com multi-stage build:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dockerfile" data-lang="dockerfile"><span class="line"><span class="cl"><span class="c"># Instala Chromium e dependências mínimas</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">RUN</span> apt-get install -y chromium fonts-liberation libnss3 libatk-bridge2.0-0 <span class="se">\
</span></span></span><span class="line"><span class="cl">    libdrm2 libxkbcommon0 libgbm1</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p><strong>Memória</strong>: Cada instância de Chromium consome 200-400MB de RAM. Se você tem 3 workers paralelos, são 1.2GB só pra browsers. Num VPS de 2GB, isso é 60% da memória.</p>
<p><strong>Latência</strong>: Uma página que leva 200ms via HTTP leva 3-8 segundos via headless browser (boot do processo, navegação, JS execution, network idle). Pra 10 fontes de dados, são 30-80 segundos ao invés de 2.</p>
<p><strong>Estabilidade</strong>: Chromium pode crashar, travar, ou vazar memória. Timeout de 30 segundos com cleanup no <code>ensure</code> é obrigatório:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">fetch</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="nb">name</span><span class="p">:)</span>
</span></span><span class="line"><span class="cl">  <span class="n">browser</span> <span class="o">=</span> <span class="n">create</span><span class="p">(</span><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="n">browser</span><span class="o">.</span><span class="n">go_to</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">yield</span> <span class="n">browser</span>
</span></span><span class="line"><span class="cl"><span class="k">ensure</span>
</span></span><span class="line"><span class="cl">  <span class="n">browser</span><span class="o">&amp;.</span><span class="n">quit</span>  <span class="c1"># SEMPRE quit, mesmo com exceçã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>Se não fizer o <code>quit</code>, o processo Chromium fica pendurado consumindo CPU e memória. Já tive servidor com 15 processos Chrome zumbis comendo 3GB de RAM porque um <code>ensure</code> estava faltando.</p>
<h2>O Futuro (Ou: Vai Piorar)<span class="hx:absolute hx:-mt-20" id="o-futuro-ou-vai-piorar"></span>
    <a href="#o-futuro-ou-vai-piorar" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A tendência é clara: sites estão ficando mais difíceis de acessar programaticamente. A cada trimestre, os DataDomes e Cloudflares da vida adicionam novas técnicas de detecção. Chrome headless fica mais parecido com Chrome real — mas os detectores acompanham.</p>
<p>A web que nasceu aberta e legível por máquina está se tornando um jardim murado. Até RSS, que é trivial de implementar e útil pra todo mundo, está sendo abandonado por muitos sites — porque não querem que seus dados sejam lidos fora do contexto (e dos anúncios) do site original.</p>
<p>Pra quem constrói sistemas que agregam informação, o playbook é:</p>
<ol>
<li><strong>Use APIs e feeds sempre que existirem</strong>. É mais estável, mais rápido, mais respeitoso</li>
<li><strong>Mantenha um browser stealth pra SPAs</strong> que não têm alternativa</li>
<li><strong>Nunca hardcode seletores CSS</strong>. Sites com CSS-in-JS mudam nomes de classe a cada deploy. Identifique elementos por propriedades estruturais (comprimento de URL, clustering, tamanho do título) que sobrevivem a redesigns</li>
<li><strong>Detecte bloqueios e falhe graciosamente</strong> — <code>og:description</code> como fallback, título extraído da URL como último recurso</li>
<li><strong>Nunca dependa de um único método de acesso</strong>. O site que funciona hoje pode bloquear amanhã</li>
<li><strong>Aceite que alguns sites são inacessíveis</strong>. Se o DataDome não deixa, o dado terá que vir de outra fonte</li>
</ol>
<p>A web em 2026 é um campo de batalha entre quem quer ler e quem quer controlar a leitura. Mas no longo prazo, a criatividade de quem quer acessar dados vence — mas custa mais esforço a cada ano. E, diferente do que eu pensei no começo: nunca é simples.</p>
]]></content:encoded><category>the-m-akita-chronicles</category><category>desenvolvimento-web</category><category>automacao</category></item><item><title>[Akitando] #133 - Tornando sua App Web Mais Rápida! | 4 Técnicas de Otimização</title><link>https://akitaonrails.github.io/2022/12/12/akitando-133-tornando-sua-app-web-mais-rapida-4-tecnicas-de-otimizacao/</link><guid isPermaLink="true">https://akitaonrails.github.io/2022/12/12/akitando-133-tornando-sua-app-web-mais-rapida-4-tecnicas-de-otimizacao/</guid><pubDate>Mon, 12 Dec 2022 09:30:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/KyqFXVVgvIs"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;Por que a aplicação que você tem feito vendo tutoriais e cursos online é BEM diferente do que equipes profissionais realmente fazem de verdade? Por que só &amp;ldquo;estar funcionando&amp;rdquo; não é o suficiente?&lt;/p&gt;
&lt;p&gt;Se você já é um profissional da área, tudo que vou dizer é o arroz com feijão, mas estou facilitando seu trabalho: compartilhe com os júniors ao seu redor. E se você é iniciante, é hora de começar a ver o que diferencia sua pequena aplicação das de verdade.&lt;/p&gt;</description><content:encoded><![CDATA[

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

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

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

<h2>DESCRIPTION<span class="hx:absolute hx:-mt-20" id="description"></span>
    <a href="#description" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Finalmente vou mostrar o que é o Heroku e como é o fluxo de trabalho mínimo de um projeto web ideal. Se você já usa Heroku, aproveite pra compartilhar o video com conhecidos que ainda não usam. Se nunca viu Heroku, prepare-se pra ficar surpreso!</p>
<h2>Conteúdo<span class="hx:absolute hx:-mt-20" id="conteúdo"></span>
    <a href="#conte%c3%bado" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li>00:00 - Intro</li>
<li>01:16 - Recapitulando Heroku</li>
<li>05:04 - Os 12 Fatores</li>
<li>06:02 - I - Codebase</li>
<li>07:11 - II - Dependencies</li>
<li>08:24 - III - Config</li>
<li>09:30 - IV - Backing Services</li>
<li>10:47 - V - Build, release, run</li>
<li>12:25 - VI - Processes</li>
<li>14:50 - VII - Port Binding</li>
<li>16:09 - VIII - Concurrency</li>
<li>17:40 - IX - Disposability</li>
<li>19:07 - X - Dev/Prod parity</li>
<li>21:59 - XI - Logs</li>
<li>22:31 - RANT: log4j</li>
<li>23:39 - XII - Admin Processes</li>
<li>24:33 - Heroku - alternativas e custo-benefício</li>
<li>28:37 - Escalabilidade não é automática</li>
<li>30:33 - 1. Farás Testes!</li>
<li>31:05 - 2. Integrarás Continuamente!</li>
<li>31:59 - 3. Usarás um Sistema de CI!</li>
<li>32:44 - 4. Farás Deploy Contínuo!</li>
<li>33:16 - Conclusão: equipes saudáveis</li>
</ul>
<h2>Links<span class="hx:absolute hx:-mt-20" id="links"></span>
    <a href="#links" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li>The Twelve-Factor App (<a href="https://12factor.net/"target="_blank" rel="noopener">https://12factor.net/</a>)</li>
<li>Guidance for preventing, detecting, and hunting for exploitation of the Log4j 2 vulnerability  (<a href="https://www.microsoft.com/security/blog/2021/12/11/guidance-for-preventing-detecting-and-hunting-for-cve-2021-44228-log4j-2-exploitation/"target="_blank" rel="noopener">https://www.microsoft.com/security/blog/2021/12/11/guidance-for-preventing-detecting-and-hunting-for-cve-2021-44228-log4j-2-exploitation/</a>)</li>
</ul>
<h2>SCRIPT<span class="hx:absolute hx:-mt-20" id="script"></span>
    <a href="#script" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Olá pessoal, Fabio Akita.</p>
<p>No episódio anterior me dei ao trabalho de fazer o passo a passo de um tutorial simples de primeiros passos usando o Heroku pra fazer deploy de um projetinho de exemplo em PHP. O objetivo foi mostrar o processo de deployment de uma aplicação moderna pra iniciantes que talvez nunca nem tenham ouvido falar de Heroku. Qualquer desenvolvedor que já trabalha com certeza já conhecia. Então, se você não sabe o que é Heroku assista o episódio anterior antes de ver este, porque precisa saber o que eu disse lá pra entender este.</p>
<p>Hoje quero falar sobre um troço conhecido como os “12 fatores” que é meio uma metodologia que pode ser aplicada a projetos web escritos em qualquer linguagem de programação e qualquer framework web. Além disso quero complementar com alguns fatores que originalmente não se menciona mas meio que ficam implícitos. Se você quer um projeto web que tenha capacidade de escalar é meio obrigatório que no mínimo esses 12 fatores estejam satisfeitos. Satisfazer os 12 fatores não torna nenhuma aplicação automaticamente escalável, mas não satisfazer eles quase garante que não vai escalar de jeito nenhum.</p>
<p>(&hellip;)</p>
<p>Se você já é programador de sistemas legados, tenho certeza que quando viu o tutorial de Heroku pela primeira vez, brilhou seus olhos de vontade de poder trabalhar desse jeito. Se você é de agenciazinhas porcaria - e eu defino como porcaria todas que ainda atualizam sites via FTP como falei antes - mesma coisa, deve estar se coçando de vontade de poder trabalhar assim. Todo mundo que trabalha em tech startups hoje em dia ou empresas grandes que se modernizaram já trabalham desta forma. Mas cuidado, se você não trabalha assim e mesmo assistindo até aqui achou tudo desnecessário ou mesmo ruim, eu repensaria a carreira. Você vai se tornar obsoleto muito em breve.</p>
<p>E quem acha que isso tudo é só mais uma novidade passageira, que vai acabar daqui a pouco, está bem enganado. Eu trabalho desse jeito desde aproximadamente 2010, portanto mais de uma década. Eu ajudei o quanto pude a difundir esse estilo de trabalho por anos e finalmente isso se tornou o padrão. Muitos tentaram imitar o Heroku, mas até hoje acho impressionante que poucos conseguiram chegar perto.</p>
<p>Só resumindo se você não lembra do episódio passado, o grande lance é você não configurar um servidor manualmente, ser tudo automatizado. E principalmente não instalar todos os componentes da aplicação no mesmo lugar, deixar cada coisa num container separado, como serviços externos independentes. Além disso conseguir fazer deploys frequentes a cada nova funcionalidade ou correção de bug. Tudo tão fácil quanto fazer um <code>git push</code>, literalmente.</p>
<p>Na mesma época que o Heroku surgiu, o Google tinha acabado de lançar um produto que fez muito barulho, o Google App Engine pra rodar aplicações feita em Python usando serviços do Google. O problema é que você não podia usar componentes padrão de Linux como banco de dados Postgres. Precisava usar o banco do Google que era o BigTable, um NoSQL que só existe no Google e não dá pra rodar em outro lugar. Além disso precisava programar de formas específicas pro App Engine. Então, uma vez que funcionasse não dava pra rodar em nenhum outro lugar. Era super simples de subir a aplicação mas era super limitado, restritivo e você ficava preso.</p>
<p>A grande sacada do Heroku foi abrir os padrões e ajudar a criar uma forma onde ao mesmo tempo você queria ficar no Heroku porque tudo era muito simples e seguro, mas não precisava lobotomizar seu código pra fazer rodar. Se quisesse sair e instalar sua própria infraestrutura, sua aplicação não tinha nada proprietário te prendendo. Além disso, eles criaram o conceito de marketplace de software as a service pra desenvolvedores. Você podia sair do Heroku, instalar sua aplicação manualmente num VPS do Linode e ainda usar o mesmo serviço de Papertrail que mostrei antes lá, por exemplo. Os addons também são independentes do Heroku.</p>
<p>Mas se ainda não ficou óbvio, você não escolhe infraestrutura querendo que seja barata demais, porque você recebe exatamente aquilo que paga. Se paga barato, não espere ter garantia de nada, aliás, espere que seu sistema vai cair quando mais precisa dele e vai sair super caro colocar tudo no ar de novo pra aguentar a carga. É por isso que sempre que possível, a recomendação é começar no Heroku. No outro extremo, se tem dinheiro sobrando, não recomendo ir direto pra um Kubernetes, começa no Heroku. Só faz infra customizada quando tem dados de uso que realmente justificam isso.</p>
<p>O Heroku mantém as coisas atualizadas. Patches de segurança e tudo mais são aplicados na sua infra, e na maioria dos casos você não precisa fazer nada. O jeito como o Heroku te obriga a organizar seu projeto, não insere nada de proprietário, mas ao mesmo tempo te força a fazer as coisas do jeito certo é a principal vantagem. Quando alguma coisa não funciona no Heroku normalmente não é um problema do Heroku, é problema da sua aplicação que foi feita de um jeito porco.</p>
<p>E pra facilitar a educação do jeito certo de programar aplicações web, o co-fundador Adam Wiggins escreveu um site chamado The 12 Factors que lista os 12 fatores que todo projeto web deve ter pra ser minimamente possível instalar numa infraestrutura escalável como o Heroku. É como se fossem 12 patterns de gerenciamento de arquitetura de projetos. Esse site foi publicado acho que em 2011 e tudo que está nele continua válido igualzinho até hoje.</p>
<p>Quem acompanha este canal e conseguiu acompanhar o tutorial de Heroku até aqui já entende a maioria, mas quero comentar ponto a ponto desses doze fatores. Se você é iniciante, preste muita atenção. É isso que se espera que todo programador web saiba de cor e salteado, independente da linguagem ou framework que escolher. Aliás, todos os frameworks web modernos de hoje facilitam você implementar esses 12 fatores. Um framework que não é compatível com esses fatores é um péssimo framework e você não deveria estar usando.</p>
<p>Vamos lá, o ponto 1 declara que todo código-fonte deve estar num repositório de versionamento de código. Hoje em dia isso só tem uma resposta e se chama Git. Tudo pode estar num único repositório Git, se for um projeto gigantesco talvez esteja dividido em múltiplos repositórios ou monorepos. Não importa, contanto que esteja em Git que todo mundo da equipe tenha acesso e que se faça deploys com frequência. Esse ponto é chave.</p>
<p>No próprio tutorial do episódio passado eu fiz pelo menos uma meia dúzia de deploys enquanto ia adicionando novas funcionalidades. Antigamente fazer deploys era um processo demorado, manual, cheio de erros humanos. Hoje em dia o certo é que se faça deploys contínuos. Múltiplos por semana, múltiplos por dia. No mínimo em ambiente que chamamos de staging, que é tipo um ambiente de testes onde tudo roda como se fosse ambiente de produção. Não pode existir uma dúzia de desenvolvedores fazendo código semanas inteiras e levar mais semanas pra subir esse código numa infraestrutura de testes. É inadmissível e é sinal claro de incompetência da gerência dessa equipe.</p>
<p>O segundo fator se chama Dependências e foi por isso que me alonguei tanto falando de gerenciamento de dependências durante o tutorial. É fator fundamental que todas as dependências de um projeto web não dependam de pacotes específicos de sistema operacional instalado na máquina. Isso porque o sistema operacional dos containers do servidor e o sistema na máquina de cada desenvolvedor sempre vão ser diferentes. E versões diferentes de cada pacotes vão estar sendo usados em cada lugar, aí a gente nunca sabe de onde está vindo muitos bugs, se é problema no código ou se é dependência com versões erradas.</p>
<p>Repetindo: toda dependência deve estar declarada num arquivo de manifesto como <code>composer.json</code> no nosso projeto de exemplo, ou <code>package.json</code> num projeto Node. E tanto na máquina do desenvolvedor quanto no script de deploy como do Heroku, vamos rodar o mesmo comando pra baixar as dependências, como o <code>compose update</code> ou <code>npm install</code> da vida. E em todos os lugares vamos garantidamente ter exatamente as mesmas dependências nas exatas versões necessárias. Isso é essencial e novamente, projetos que não estão assim demonstram incapacidade da gerência do projeto.</p>
<p>O terceiro fator se chama Configuração. Em resumo, foi o que expliquei no tutorial sobre a diferença de configuração na máquina de desenvolvimento usando recursos do framework que carrega arquivos como <code>.env</code> ou algum arquivo JSON da vida e variáveis de ambiente. Todo framework competente permite executar a aplicação em ambientes distintos pra desenvolvimento, testes e produção, no mínimo. Isso porque em desenvolvimento queremos mais detalhes de erros, mais bibliotecas de debugging e monitoramento, mas em produção não queremos divulgar nenhum detalhe de erros e nem desperdiçar memória carregando bibliotecas que só servem em desenvolvimento.</p>
<p>Em cada um desses ambientes precisamos de variáveis de configuração diferentes, pro ambiente de testes precisamos conectar num banco de dados de teste, por exemplo. Ou não mandar SMS ou notificações de verdade e só logar que tentou mandar. Tudo isso precisa estar facilmente configurável em variáveis de ambiente. Leia os detalhes sobre configuração do seu framework, tem muitas nuances que você precisa estudar e testar com calma.</p>
<p>O fator 4 é mais relevante se você já é um programador das antigas que instalava tudo num servidor como já expliquei inúmeras vezes que não deveria, com stacks como LAMP. Pra você havia diferença num banco de dados que roda no mesmo localhost da sua aplicação web e um serviço externo de enviar e-mails, por exemplo. Numa aplicação 12 fatores não existe essa distinção. Tudo é um serviço externo, ou um recurso externo, acessível via uma URL. Nada roda junto do container da sua aplicação web, tudo roda externamente. Esse é o quarto fator, backing services.</p>
<p>É extritamente proibido instalar um servidor MySQL junto com um Redis ou um sistema de filas como RabbitMQ. No mínimo do mínimo, se tiver um hardware parrudo, cada serviço deve estar rodando isolado no seu próprio container ou máquina virtual. Mesmo na sua máquina de desenvolvimento, se possível não instale tudo junto. Rode cada coisa num container Docker e orquestre tudo com Docker Compose. Estude sobre Docker Compose e experimente transformar seu projeto tradicional num conjunto de containers. É até uma boa forma de garantir que você não largou conexões hardcoded pra localhost, que vai funcionar na sua máquina de desenvolvimento mas vai falhar em produção.</p>
<p>O fator 5 é mais específico pra quem mexe com infra, mas é sobre separar a fase de build da fase de execução. Lembra como toda vez que fazemos <code>git push heroku</code> eu falei que o Heroku criava uma nova imagem, semelhante ao que o Docker faz quando executamos <code>docker build</code>? É isso que quero dizer. Antigamente a gente faria o equivalente build manualmente copiando arquivos via FTP e logo na sequência já executaríamos a aplicação e deixaríamos lá rodando.</p>
<p>Pior, se tivesse algum bugzinho, o normal era abrir um SSH pro servidor ou, Deus me livre, via Telnet, editar algum arquivo manualmente direto lá e pronto. Era super fácil e também a raíz de milhares de problemas. Primeiro porque tudo que você editou na mão direto no servidor não está no repositório de código. Se der pau no servidor, você perde a modificação e tudo pára de funcionar e ninguém sabe porque. Voltamos ao fator 1 que declara que 100% de tudo deve sempre estar num repositório Git.</p>
<p>Segundo porque digamos que queremos voltar uma release, como expliquei que o Heroku faz. Não tem como, porque todo novo deploy sobrescreve em cima da versão antiga de maneira permanente. Mas gerando builds, sempre vamos ter uma cópia da imagem antiga e podemos facilmente voltar pra ela. E como cada imagem não depende de pacotes específicos do sistema operacional, tudo que ela precisa pra rodar está na imagem. Portanto trocar imagens é uma operação trivial que pode ser feita dezenas de vezes e vai funcionar em todas as vezes, sem depender de nenhuma pessoa entrando nos servidores e subindo coisas manualmente.</p>
<p>O fator 6 provavelmente é um dos mais difíceis de entender se você for um iniciante. Ela se chama Processos mas na verdade é o fator que define que sua aplicação deve ser share-nothing, ou mais corretamente, stateless, sem estado. O exemplo mais simples é que sua aplicação nunca deve gravar nada no sistema de arquivos. Por exemplo, digamos que você faça uma página pra fazer upload de fotos pra colocar na página de perfil de cada usuário.</p>
<p>Você segue um tutorial qualquer que manda ir gravando tudo num diretório chamado uploads no servidor. E tudo vai funcionar perfeitamente. Até você fazer um novo deploy no Heroku ou outro sistema de containers e aí vai descobrir que todas as suas fotos sumiram. Os únicos arquivos que existem na sua aplicação são aqueles que existiam na fase de build da imagem, que é o código fonte que baixou do Git, as bibliotecas que um gerenciador de dependências como o Composer baixou, e outros arquivos gerados por scripts executados no build, como arquivos de javascript compilados por um Webpack.</p>
<p>Uma vez que a build foi fechada, ela não pode mais ser modificada, é pra ser considerada read-only. Daí vocês vão lembrar que eu falei durante o tutorial que o Heroku vai derrubar os dynos que estavam executando a versão antiga e subir novos dynos com a imagem nova. Entenderam? Quando o container é desligado, tudo que estava nele é perdido, por exemplo as fotos no diretório uploads. Quando um novo dyno subir, os únicos arquivos que vão ser carregados são os que estão na imagem que acabamos de construir.</p>
<p>No caso de um Docker na sua máquina local é a mesma coisa, mas você tem a opção de mapear um diretório externo fora do container e remontar esse diretório quando subir o container de novo, daí os arquivos ainda persistem, mas sempre fora do container. Tudo dentro de um container deve ser considerado efêmero.</p>
<p>Todo dado que você quer que sobreviva a um deploy deve obrigatoriamente estar num armazenamento externo, um backing service como descrito no fator 4. Por exemplo num banco de dados como Postgres ou Redis e, no caso de arquivos, num serviço como o AWS S3 ou Azure Blob ou Google Cloud Storage da vida. Tem várias outras opções. Mesma coisa logs, por isso o certo é adicionar addons como um Papertrail. Qualquer log que for gravado como arquivo dentro do container vai ser perdido no próximo deploy, e esse é o jeito certo de se fazer aplicações web. Vamos falar mais sobre log no final.</p>
<p>O fator 7 não é tanto uma preocupação pra nós desenvolvedores, especialmente hoje em dia. Ele só declara que todo serviço que precisamos deve estar exposto numa porta de rede. Como nossa aplicação web que costuma estar exposta em portas como 3000 ou 8080, ou MySQL que costuma estar na porta 3601 e assim por diante.</p>
<p>Isso eu acho que é mais pra dizer que não devemos usar serviços que não estejam expostos em portas. Por exemplo, pense no serviço cron que existe em todo Linux e serve pra schedular tarefas. A gente configura num arquivo pra executar algum comando em alguma determinada hora do dia ou periodicamente, tipo todo dia às 4 da manhã se for um backup. O único ponto de contato com esse serviço é via um arquivo de configuração, por isso não podemos usar cron numa infraestrutura dessas, com vários containers que não permite modificar arquivos localmente.</p>
<p>Em vez disso vamos usar um software as a service, um SaaS, que adiciona uma aplicação web na frente do cron, ele vai ser instalado como um backing service, um serviço externo e nossa aplicação web vai interagir com esse serviço via HTTP, conectando na porta web dele. Assim, a requisição pode sair de qualquer um dos nossos containers web e todos vão consistentemente chegar no mesmo serviço sempre, assim como já chegam em outros serviços como o banco de dados.</p>
<p>O fator 8 se chama Concorrência e meio que só deriva dos fatores anteriores. Ele declara que a forma de escalar é horizontal. Ou seja, como nossa aplicação é share-nothing, ou seja, não depende de nenhum estado específico da máquina onde está, e como já é tudo organizado como builds que podemos subir como containers em qualquer máquina, podemos rapidamente aumentar ou diminuir a quantidade de containers horizontalmente.</p>
<p>Esse fator é inspirado no jeito UNIX de pensar onde tudo são processos e a forma de escalar é dar fork em mais processos. Mesmo quando usamos alguma linguagem como Javascript ou Elixir, que permite fazer multiplexing via threads ou I/O assíncrono dentro de cada processo, ainda assim sempre vai estar limitada pelo tamanho da máquina onde está rodando e quando esse recurso se exaurir, vai precisa escalar horizontalmente de qualquer jeito. Escalabilidade exige expansão e contração horizontal, que é mexer na quantidade de containers e/ou na quantidade de máquinas físicas.</p>
<p>Particularmente um Heroku facilita isso porque quando você escala horizontalmente containers web, vai precisar de um load balancer na frente pra distribuir a carga de requisições. É algo que se você só subiu aplicações pequenas nunca teve que pensar, porque só tinha um servidor web e todas as requisições iam direto pra ele. Com mais um servidor web, você precisa de um balanceador de carga como NGINX ou HAProxy na frente. Um Heroku da vida já te dá esse balanceador por padrão de forma transparente.</p>
<p>O fator 9 é um pouco mais avançado de entender e eu recomendo estudar sobre sinais de processos como SIGTERM e o que é shutdown gracioso e conceitos como rolling restart. Mas na prática esse fator fala sobre Descartabilidade. Você já viu isso na prática no tutorial toda vez que fizemos <code>git push heroku</code>. Ele faz a build de uma nova imagem e descarta os containers antigos pra subir novos. É isso que significa ser descartável. Uma vez que garantimos que não dependemos de nada dentro do container, todos os dados estão seguros num banco de dados ou outros serviços como o AWS S3 pra arquivos remotos, podemos facilmente descartar containers pra fazer deploy de uma nova release ou dar rollback pra releases anteriores.</p>
<p>Antigamente, quando a gente instalava tudo num mesmo servidor, não dava pra fazer isso. E se a máquina era invadida por um hacker? É impossível recuperar uma máquina comprometida, o certo é jogar tudo fora e remontar do zero. Mas se você editava coisas via SSH direto no servidor, se parte do que rodava não estava no Git, e se sua aplicação gravava uploads e logs localmente como arquivos. E agora? Como você vai conseguir reinstalar essa máquina o mais rápido possível? E a resposta é simples: não vai. Um sistema robusto precisa ser descartável. Você precisa conseguir apagar o servidor inteiro e reinstalar tudo automaticamente via script sem perder 1 bit de dados e em poucos segundos.</p>
<p>O ponto 10 é pra reforçar o que acabei de falar, que é manter ambientes de desenvolvimento, staging e produção o mais similares quanto possível. E isso obviamente significa tudo em repositório Git, significa builds automatizados e significa jamais editar arquivos direto no servidor. Os benefícios de fazer as coisas dessa forma é que gerenciar infraestrutura se torna muito mais fácil e quase trivial em alguns casos. É assim que nasce a área de devops de verdade.</p>
<p>Mais do que isso, esse fator significa fazer deploys pra staging e produção o mais rápido quanto possível pra estar próximo do que está rodando na máquina dos desenvolvedores. Nada disso de demorar um mês ou mais pra fechar uma release e mandar pra produção. Estude sobre Continuous Deployment. Plataformas como GitHub e GitLab tem configurações pra criar builds e rodar os testes da sua aplicação a cada novo commit que aparece. E se tudo passar eles fazem automaticamente o <code>git push heroku</code> pra staging ou pra produção.</p>
<p>Parece perigoso isso de subir pros servidores automaticamente mas só é perigoso se sua gerência é uma droga. A quantidade de bugs em produção reflete inversamente a qualidade de comunicação da sua equipe. Uma equipe disfuncional vai subir porcaria pra produção não importa quantos mecanismos de gerenciamento você coloque em cima deles.</p>
<p>Uma equipe saudável revisa o código um do outro antes de mergear um pull request na branch <code>main</code> do Git. Uma equipe saudável sempre adiciona testes pra cada nova funcionalidade e sempre adiciona testes que simulam um bug recém reportado, dessa forma esse bug não vai aparecer de novo no futuro. Uma equipe saudável implementa novas funcionalidades atrás um &ldquo;feature flag&rdquo;. Seja via variável de ambiente ou via permissão pra determinados usuários testarem e pros demais usuários não conseguirem enxergar essa nova funcionalidade até toda equipe ter chance de testar e aprovar. Ou seja, mesmo a desculpa que uma nova funcionalidade vai levar semanas pra ficar pronto não é justificativa suficiente pra não estar integrando o código no repositório com frequência e não estar fazendo deploys com frequência. Quanto mais se demora pra integrar e deployar mais rápido aumenta a quantidade de bugs e conflitos, é inevitável.</p>
<p>Existem diversas estratégias de comunicação e organização que uma equipe saudável pode aplicar e eu acabei de descrever algumas. Estude sobre Testes, Integração Contínua e Deploy Contínuo. Só equipes disfuncionais e/ou com péssima gerência são incapazes de trabalhar de forma integrada e contínua. Lembrem-se, não importa a linguagem ou framework, produtividade, bugs, tudo isso é reflexo da qualidade de comunicação de uma equipe. Sempre.</p>
<p>O fator 11 de novo é meio específico pra quem desenvolve frameworks, mas fala sobre logs serem streams em vez de arquivos. Todo bom sistema manda logs pro STDOUT. Se por acaso você começou a aprender sobre logs, rotação de logs e coisas assim, esquece. Num container todo serviço deve escrever logs pro STDOUT, sempre. Daí a plataforma de infraestrutura, como um Heroku, vai capturar esses streams e mandar pra algum serviço externo de logs, como o Papertrail que mostrei antes.</p>
<p>Fica a dica que um sistema de log que tenta fazer coisas demais uma hora vai dar catástrofe. Que foi o que aconteceu com o caso recente do Log4j. É bem coisa de javeiro enterprise fazer uma mísera biblioteca de Log que, por alguma razão do além, permite executar comandos que vem no log e ainda vem junto com bibliotecas como de lookup de JNDI que se conecta com o mundo exterior. É o cúmulo da estupidez, pra dizer o mínimo.</p>
<p>Um log4j nem precisa muito existir, pelo menos não do jeito como é hoje. Todo log deveria ir pro STDOUT e um serviço externo, separado, como Logstash, Kibana e coisas assim que deveriam consumir esses logs e organizar. Isso é fruto daquela mentalidade de arquiteto chimfrin que pensa, &ldquo;ah, e se amanhã eu precisar dessa opção?&rdquo; E isso tá errado. Nunca faça coisas que não precisa pra hoje. O dia que a necessidade aparecer aí pensa qual a melhor forma de resolver, mas não faça nada porque tem chances de amanhã precisar. Isso é software desnecessário, e todo software desnecessário é um buraco de segurança esperando pra ser descoberto.</p>
<p>Finalmente o fator 12, que fala de processos de administração, que eu já mostrei no tutorial pra vocês. A idéia é que você nunca abre um SSH direto pra um dos containers web. Toda tarefa administrativa fora do comum deve ser executado num container isolado e separado dos demais, sem concorrer os mesmos recursos com as requisições de usuários.</p>
<p>Lembram quando rodei <code>heroku run bash</code> que abriu um container direto no bash? Ou quando rodei <code>heroku pg:psql</code> que abriu o console do Postgres pra criar a tabela que faltava? Em ambos os casos o Heroku subiu um novo container com a imagem da última release, ou seja, idêntico ao conteúdo dos containers web, mas fora do load balancer. Assim eu rodo no mesmo ambiente e quando terminar e desconectar, esse container é automaticamente destruído. E isso é importante, lembra o fator sobre descartabilidade?</p>
<p>Os 12 fatores foram muito influentes quando foram lançados porque na época o único framework que implementava com sucesso todos os fatores era o Ruby on Rails. Levou um tempão pros outros alcançarem esse mínimo. Produtos open source que não usavam nenhum framework como Wordpress ou Magento não implementavam todos os 12 fatores por padrão e também levou um tempo pra se adequarem.</p>
<p>O Heroku foi lançado entre 2008 e 2009, só uns 2 anos depois que a Amazon AWS apareceu. Naquela época a maioria de nós só tinha mentalidade de servidor virtual, no máximo, VPS. Ninguém pensava em particionar recursos da mesma máquina em containers e muito menos precificar por containers. Docker ainda estava a anos de ser lançado e na verdade muito do Docker foi influenciado pelo Heroku.</p>
<p>A Amazon estava muito a frente do seu tempo, tanto que ninguém sabia exatamente qual a melhor forma de tirar proveito dessa idéia de infraestrutura elástica. O que significava ser elástico? Como lidar com máquinas voláteis e descartáveis que quando reiniciam apagam tudo que tinha dentro? O Heroku foi a primeira plataforma como serviço ou PaaS que de fato soube aproveitar a infraestrutura que a AWS estava oferecendo.</p>
<p>O sonho de muitas empresas é ter um Heroku privado, o famoso private cloud, onde equipes internas poderiam facilmente dar <code>git push</code> e o deploy aconteceria automaticamente. E muitas empresas já trabalham assim. É o que todo mundo que tenta instalar Kubernetes busca. O que o Heroku chama de dynos, o Kubernetes chama de pods. A linha de comando <code>kubectl</code> da vida é inspirado na linha de comando do Heroku. Enfim, se você lida com devops de alguma forma hoje em dia, tenha certeza que muita coisa foi inspirado no Heroku.</p>
<p>Se parece fanboy falando, pode dizer que sou mesmo, porque em mais de 10 anos entregando projetos de verdade pra clientes, ainda não vi outra solução que chega perto em termos de simplicidade, flexibilidade, organização e custo benefício. Muitos acham caro a faixa de 30 dólares por dyno e é mesmo, e fazem a conta comparando com o preço bruto da hora de uma máquina no EC2. Mas a conta tá errada. A conta certa é calcular quanto custa ter uma pessoa experiente de devops de plantão todo dia pra manter sua infra. O cálculo certo não é da máquina mas do cara de devops que você não precisa ter quando usa o Heroku.</p>
<p>Aliás, antes que alguém vá nos comentários dizer sim, eu sei que existem soluções como o Dokku que implementa um Heroku light que você mesmo pode instalar num VPS da vida. Você também pode usar direto Docker Machine pra orquestar suas VPS. Mas de novo, você precisa adicionar o custo de alguém de infra pra dar manutenção nessa infraestrutura. Nunca vai ser plug and play que uma vez instalado ninguém mais precisa mexer. Não caia nessa armadilha.</p>
<p>Além disso tem soluções como o Google Firebase que, como o Google App Engine antes dele, você programa especificamente pra essa plataforma, e vai depender pra sempre do que o Google te oferecer. Esquece querer mudar pro Azure ou AWS depois. E esquece querer usar qualquer ferramenta open source e bibliotecas que a plataforma não suporta. Tudo tem prós e contras e meu ponto é que o Heroku oferece o melhor custo benefício entre conveniência, segurança e independência do seu projeto.</p>
<p>Também vale relembrar que Heroku e 12 fatores não são balas de prata, eu tenho um caso pra ilustrar. Lá por 2014 estava pessoalmente trabalhando num projeto pra cliente que envolvia uma ferramenta que seria usada por vestibulandos. Era uma plataforma de educação e os donos tinham a expectativa que ia vir uma montanha de alunos de cursinho por causa de algum evento que eu não lembro mais o que era. Mas em resumo eles pediram especificamente pro sistema ser escalável.</p>
<p>Eu e minha equipe fizemos tudo em Ruby on Rails, que é o framework que funciona melhor no Heroku, especialmente naquela época, porque o próprio Heroku é em boa parte feito em Rails também. Fizemos deployment mas na hora do vamos ver sofremos um gargalo. Obviamente foi culpa minha que não estimou direito o tamanho do bancos de dados Postgres.</p>
<p>O problema é o seguinte: todo servidor de banco de dados tem um máximo de conexões simultâneas que aguenta. Faz de conta que são 100. Mesmo implementando um connection pool, se eu escalar horizontalmente a aplicação, ou seja, aumentar o número de dynos web rodando, todos vão se pendurar no banco de dados e rapidamente 100 conexões vão ficar ocupados e novos dynos vão ficar travados esperando conseguir uma conexão.</p>
<p>Pra lidar com isso a gente tenta mover o que não precisa pegar direto do banco em caches como Redis. E se precisa muito do banco, o certo é subir vários outros servidores que são réplicas só de leitura do principal. Assim cada nova réplica aguenta mais 100 conexões simultâneas, por exemplo. Mas não dá pra colocar réplicas infinitamente porque quanto mais réplicas tem, maior o trabalho de replicação dos dados, aí vai dar gargalo na sincronia de replicação.</p>
<p>Muito iniciante que entra em plataformas como Heroku achando que vai colocar um addon de auto-scale e tudo vai escalar infinitamente sozinho, se decepciona que não funciona mágico assim. Escalabilidade é uma combinação de funcionalidades de infraestrutura e arquitetura bem implementada. Depende mais do programador do que da infraestrutura. Mas se você for um bom programador, um Heroku dá tudo que você precisa com o mínimo de esforço. É impressionante o que eu consigo fazer sozinho num ambiente desse em 5 min, coisa que no começo dos anos 2000 ia me custar dias de configuração. Já se você for um programador ruim, nenhum Heroku ou solução de cloud vai te ajudar, só vai ser um band-aid temporário.</p>
<p>Eu já venho repetindo isso faz alguns episódios e vou repetir de novo. Eu tenho projetos pra clientes que funcionam assim, mas o certo como já disse antes é que os ambiente de staging e produção não estejam muito longe da versão em desenvolvimento na máquina dos desenvolvedores. Pra que isso seja possível, precisa obrigatoriamente existir os seguintes passos.</p>
<p>Primeiro passo, toda nova funcionalidade e toda correção de bugs deve vir acompanhado no mínimo de testes unitários. Todo pull request sem testes deve ser automaticamente rejeitado. Sem exceções. Essa é a principal regra que vai garantir que seu projeto sobreviva por muitos anos. No começo, com pouca gente, com pouco código e com prazos apertados, parece que é uma grande perda de tempo. E é assim que amadores pensam e é assim que amadores se ferram quando o projeto rapidamente cresce fora de controle.</p>
<p>Quando você satisfaz essa primeira condição agora precisamos do segunda passo: que é integrar o código constantemente e frequentemente. Ou seja, nenhum desenvolvedor deve ficar dias e dias com código que só existe na sua máquina, demorando pra dar push pro repositório no GitHub ou GitLab. Todo desenvolvedor deve, no mínimo uma vez por dia, dar pull, ou seja, puxar as últimas atualizações do repositório pra pegar tudo que todo mundo da equipe andou trabalhando e dar rebase com seu branch de desenvolvimento e corrigir conflitos. E com sua suite de testes unitários, rapidamente ver o que quebrou e consertar imediatamente. Se você faz isso com frequência, os conflitos são simples de resolver, se der conflitos. Quanto mais tempo demora pra integrar, mais difícil vai sendo pra resolver os conflitos do seu código com dos outros programadores da equipe, e maiores as chances de fazer bosta e subir bugs.</p>
<p>Isso é integração contínua. Facilita muito se como regra número três você estiver usando algo como GitLab que tem suporte a rodar testes automaticamente no servidor, ou usar qualquer outra solução como CircleCI. Você deixa um Dockerfile configurado no projeto, o GitLab vai buildar toda vez que alguém der push pro repositório e colocar numa fila pra rodar todos os testes automatizados. Se falhar já notifica pra todo mundo e assim garantimos que todo código que está na branch <code>main</code> sempre tem todos os testes rodando e passando. O objetivo é esse: que sempre que alguém der clone do branch <code>main</code>, tudo sempre vai funcionar. O branch <code>main</code> tem que ser sagrado. Tudo que está inacabado deve estar em branches de desenvolvimento separados.</p>
<p>É essa condição que permite a próxima regra número quatro que é, toda vez que todos os testes automatizados passarem, deve ser feito deployment pra um ambiente de staging. O Heroku suporta criar pipelines de staging e production, assim você sempre tem uma infraestrutura idêntica de testes e de produção. Daí um GitLab da vida mesmo faz o <code>git push heroku</code> automaticamente pra staging e sua equipe pode imeditamente testar no navegador como um usuário normal pra ver se realmente nada quebrou, se as novas funcionalidades estão mesmo funcionando.</p>
<p>Recapitulando: uma equipe saudável e produtiva vai sempre desenvolver código com testes automatizados, vai sempre integrar continuamente, vai sempre ter um servidor de testes como GitLab que roda a cada push, de cada desenvolvedor. E finalmente, vai sempre ter deployments automáticos pra ambiente de staging toda vez que todos os testes automatizados passam. E fica como opção subir pra produção automaticamente ou deixar alguém de QA,
por exemplo, responsável por fazer testes manuais no final e apertar o botão pra deployar pra produção e liberar pros usuários.</p>
<p>Um modelo similar ao Heroku, com um projeto arquitetado pra atender os 12 fatores, e com minhas 4 últimas regras é o que eu chamo do framework mínimo pra toda equipe de desenvolvimento verdadeiramente Ágil. Toda outra metodologia, sistema de backlog e controles, é opcional e não relevante pra produtividade de verdade. Lembrem-se, a qualidade do software é diretamente proporcional à qualidade da comunicação da equipe. Não tem como fugir disso. Se sua equipe se comunica mau e porcamente, seu software vai ser necessariamente uma grande droga.</p>
<p>A idéia hoje foi só apresentar pra quem é iniciante como que profissionais de verdade trabalham ou gostariam de trabalhar. E é esse nível que vocês deveriam almejar alcançar. Se você trabalhar num lugar que não funciona assim, tente evangelizar o jeito certo. Vai implementando um aspecto de cada vez, vai fazendo limpeza um pedaço de cada vez. Mesmo nos projetos mais antigos e mais porcos é possível transformar num projeto eficiente se, primeiro, a equipe entende que dá pra melhorar e se compromentem a fazer limpeza e não aumentar mais a sujeira. Por hoje é isso aí. Se ficaram com dúvidas mandem nos comentários abaixo, se curtiram o video deixem um joinha, assinem o canal e compartilhem o video com seus amigos. A gente se vê, até mais.</p>
]]></content:encoded><category>arquitetura-de-software</category><category>devops</category><category>desenvolvimento-web</category><category>akitando</category></item><item><title>[Akitando] #112 - Subindo Aplicações Web em Produção | Aprendendo HEROKU</title><link>https://akitaonrails.github.io/2022/01/10/akitando-112-subindo-aplicacoes-web-em-producao-aprendendo-heroku/</link><guid isPermaLink="true">https://akitaonrails.github.io/2022/01/10/akitando-112-subindo-aplicacoes-web-em-producao-aprendendo-heroku/</guid><pubDate>Mon, 10 Jan 2022 12:15:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/TLRW_xTnQwY"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;Finalmente vou mostrar o que é o Heroku e como é o fluxo de trabalho mínimo de um projeto web ideal. Se você já usa Heroku, aproveite pra compartilhar o video com conhecidos que ainda não usam. Se nunca viu Heroku, prepare-se pra ficar surpreso!&lt;/p&gt;
&lt;h2&gt;Conteúdo&lt;span class="hx:absolute hx:-mt-20" id="conteúdo"&gt;&lt;/span&gt;
&lt;a href="#conte%c3%bado" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;00:00 - Intro&lt;/li&gt;
&lt;li&gt;00:41 - Mega Sena da Virada&lt;/li&gt;
&lt;li&gt;02:33 - Concorrência&lt;/li&gt;
&lt;li&gt;04:11 - Intro a Heroku&lt;/li&gt;
&lt;li&gt;04:46 - Deploy antigo&lt;/li&gt;
&lt;li&gt;06:24 - Iniciando Tutorial Heroku&lt;/li&gt;
&lt;li&gt;06:49 - Setup: Git é Básico!&lt;/li&gt;
&lt;li&gt;07:22 - Heroku Login&lt;/li&gt;
&lt;li&gt;08:18 - Primeiro Deploy&lt;/li&gt;
&lt;li&gt;10:03 - Buildpack? Dockerfile?&lt;/li&gt;
&lt;li&gt;10:35 - Entendendo Dynos e Containers&lt;/li&gt;
&lt;li&gt;11:30 - Escalando Dynos&lt;/li&gt;
&lt;li&gt;13:16 - Entendendo Load Balancer&lt;/li&gt;
&lt;li&gt;16:41 - Vendo logs dos Dynos&lt;/li&gt;
&lt;li&gt;17:22 - Entendendo Procfiles&lt;/li&gt;
&lt;li&gt;18:58 - Mais sobre escalar dynos&lt;/li&gt;
&lt;li&gt;20:21 - Entendendo Gerenciamento de Dependências&lt;/li&gt;
&lt;li&gt;24:37 - Adicionando nova dependência&lt;/li&gt;
&lt;li&gt;26:40 - Adicionando Addons&lt;/li&gt;
&lt;li&gt;28:16 - Conectando num shell remoto&lt;/li&gt;
&lt;li&gt;29:41 - Configurando com variáveis de ambiente&lt;/li&gt;
&lt;li&gt;33:39 - Adicionando Banco de Dados&lt;/li&gt;
&lt;li&gt;37:13 - Database Migrations&lt;/li&gt;
&lt;li&gt;37:47 - Conectando no Banco de Dados&lt;/li&gt;
&lt;li&gt;39:22 - Releases e Rollback&lt;/li&gt;
&lt;li&gt;40:34 - Conclusão&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Links&lt;span class="hx:absolute hx:-mt-20" id="links"&gt;&lt;/span&gt;
&lt;a href="#links" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Mega da Virada: Loterias Caixa colocam fila em app, e site fica fora do ar (&lt;a href="https://tecnoblog.net/noticias/2021/12/31/mega-da-virada-loterias-caixa-colocam-fila-em-app-e-site-fica-fora-do-ar/"target="_blank" rel="noopener"&gt;https://tecnoblog.net/noticias/2021/12/31/mega-da-virada-loterias-caixa-colocam-fila-em-app-e-site-fica-fora-do-ar/&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Getting Started on Heroku with PHP (&lt;a href="https://devcenter.heroku.com/articles/getting-started-with-php#set-up"target="_blank" rel="noopener"&gt;https://devcenter.heroku.com/articles/getting-started-with-php#set-up&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;SCRIPT&lt;span class="hx:absolute hx:-mt-20" id="script"&gt;&lt;/span&gt;
&lt;a href="#script" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Olá pessoal, Fabio Akita&lt;/p&gt;</description><content:encoded><![CDATA[

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

<p>Finalmente vou mostrar o que é o Heroku e como é o fluxo de trabalho mínimo de um projeto web ideal. Se você já usa Heroku, aproveite pra compartilhar o video com conhecidos que ainda não usam. Se nunca viu Heroku, prepare-se pra ficar surpreso!</p>
<h2>Conteúdo<span class="hx:absolute hx:-mt-20" id="conteúdo"></span>
    <a href="#conte%c3%bado" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li>00:00 - Intro</li>
<li>00:41 - Mega Sena da Virada</li>
<li>02:33 - Concorrência</li>
<li>04:11 - Intro a Heroku</li>
<li>04:46 - Deploy antigo</li>
<li>06:24 - Iniciando Tutorial Heroku</li>
<li>06:49 - Setup: Git é Básico!</li>
<li>07:22 - Heroku Login</li>
<li>08:18 - Primeiro Deploy</li>
<li>10:03 - Buildpack? Dockerfile?</li>
<li>10:35 - Entendendo Dynos e Containers</li>
<li>11:30 - Escalando Dynos</li>
<li>13:16 - Entendendo Load Balancer</li>
<li>16:41 - Vendo logs dos Dynos</li>
<li>17:22 - Entendendo Procfiles</li>
<li>18:58 - Mais sobre escalar dynos</li>
<li>20:21 - Entendendo Gerenciamento de Dependências</li>
<li>24:37 - Adicionando nova dependência</li>
<li>26:40 - Adicionando Addons</li>
<li>28:16 - Conectando num shell remoto</li>
<li>29:41 - Configurando com variáveis de ambiente</li>
<li>33:39 - Adicionando Banco de Dados</li>
<li>37:13 - Database Migrations</li>
<li>37:47 - Conectando no Banco de Dados</li>
<li>39:22 - Releases e Rollback</li>
<li>40:34 - Conclusão</li>
</ul>
<h2>Links<span class="hx:absolute hx:-mt-20" id="links"></span>
    <a href="#links" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li>Mega da Virada: Loterias Caixa colocam fila em app, e site fica fora do ar (<a href="https://tecnoblog.net/noticias/2021/12/31/mega-da-virada-loterias-caixa-colocam-fila-em-app-e-site-fica-fora-do-ar/"target="_blank" rel="noopener">https://tecnoblog.net/noticias/2021/12/31/mega-da-virada-loterias-caixa-colocam-fila-em-app-e-site-fica-fora-do-ar/</a>)</li>
<li>Getting Started on Heroku with PHP (<a href="https://devcenter.heroku.com/articles/getting-started-with-php#set-up"target="_blank" rel="noopener">https://devcenter.heroku.com/articles/getting-started-with-php#set-up</a>)</li>
</ul>
<h2>SCRIPT<span class="hx:absolute hx:-mt-20" id="script"></span>
    <a href="#script" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Olá pessoal, Fabio Akita</p>
<p>Bem vindos ao primeiro video de 2022. O video de hoje vai ser parte 1 de 2 videos. Eu queria falar sobre os famosos 12 fatores pra criar projetos escaláveis modernos. Pra fazer isso eu preciso que todo mundo já tenha visto pelo menos uma vez como é trabalhar com uma plataforma chamada Heroku. Se você já trabalha com Heroku, o que vou falar hoje é o arroz com feijão, mas vocês podem usar pra apresentar a plataforma pra amigos seus que ainda não conhece. Pra todo o resto, esse é o modelo de trabalho que você deveria almejar se nunca usou essa plataforma.</p>
<p>(&hellip;)</p>
<p>Antes de começar deixa eu responder algumas coisas que vi online depois dos últimos vídeos. No caso do exemplo que dei do ingresso.com eu terminei concluindo que se eles não tem controle sobre o sistema de reserva de assentos dos cinemas - que imagino que sejam aplicativos super antigos que não foram feitos pra escala de internet - então deveriam controlar o acesso a esses sistemas usando uma fila virtual. Ninguém acessa diretamente a reserva de assentos, cai primeiro numa página mais fácil de escalar de fila e fica esperando a vez.</p>
<p>Por acaso foi exatamente isso que a Caixa Econômica Federal fez pra Mega Sena da virada que ia sortear mais de 300 milhões de reais. E eles implementaram justamente uma fila virtual pra ver se aguentavam a multidão que ia tentar acessar tudo no último dia. Eu não perco meu tempo apostando em loteria porque é só garantia que vou estar dando dinheiro de graça pros outros, mas quem tentou parece que teve muita dor de cabeça mesmo tendo esse sistema de fila implementado.</p>
<p>Mas é óbvio. Só porque você implementou algum pattern ou arquitetura não existe nenhuma garantia que implementou certo ou que não exista nenhum outro problema que ficou pra trás. Eu não sei como eles fizeram, mas imagino pelo menos 2 problemas. O primeiro e mais óbvio é que o tráfego gerado por uma Mega Sena dessas deve ter sido absurdamente alto. No nível que até uma Amazon da vida sofreria um pouco. Então dou o benefício da dúvida que eles tentaram o máximo mas mesmo assim o tráfego foi ainda maior do que era imaginado.</p>
<p>O segundo problema, que acho que é o mais factível, é que o sistema foi mal feito mesmo. Não me entendam mal. Eu acho que existem sim, bons programadores, com boas capacidades técnicas e boas intenções que tentaram o máximo pra fazer isso funcionar. Porém, a grande maioria dos funcionários públicos que trabalham nos diversos departamentos e sistemas integrados provavelmente não tavam dando a mínima e foram obstáculos pros poucos que tentaram fazer as coisas direito.</p>
<p>Entenda a seguinte verdade: nenhum sistema feito pelo estado jamais vai ser tão bom quanto os melhores sistemas feitos na iniciativa privada. É uma impossibilidade. O motivo é muito simples: não tem concorrência. A mesma coisa vale pra empresas privadas que tem conluio com governos, que passam a ser basicamente empresas públicas com fachada de empresa privada. A maioria dos grandes monopólios só existem porque foram auxiliados pelos governos.</p>
<p>Sem concorrência, pra que eu preciso fazer um sistema melhor? As pessoas têm outra opção? Não, então foda-se, vai ser obrigado a usar o que tem. Mesmo se for uma merda. E eu, que trabalho aqui, vou ser mandado embora se não funcionar? Também não, então foda-se de novo. E vai ser sempre assim. Eu sinto pena dos poucos que trabalham lá que querem fazer as coisas funcionar direito, mas vão sempre esbarrar em um monte de departamento que tá pouco se fodendo e um monte de burocracia que impede melhorias. Mas é isso aí.</p>
<p>Software nunca tá “pronto”. Se o lugar tem mentalidade de &ldquo;terminar&rdquo; o software e não mexer mais, sempre vai ser uma porcaria. O Software precisa estar constantemente atualizado, constantemente consertado, constantemente expandindo e remodelado. O problema em lugares como uma Caixa é que deve ter dezenas de sistemas que tá no esquema &ldquo;tá funcionando? então não mexe!&rdquo; só que eventualmente vai precisar integrar, se comunicar. Mas não tem como, daí vai nascendo dezenas de gambiarras pra fazer as coisas funcionarem ao redor dessas ilhas radioativas. Deixe passar alguns anos assim e você tem a experiência bosta que foi a Mega da virada. É simples assim.</p>
<p>E isso me lembrou de um assunto que eu queria explicar faz algum tempo. No video sobre a história do Ruby on Rails eu mencionei rapidamente sobre Heroku e a metodologia de 12 fatores do Adam Wiggins, um dos co-fundadores do Heroku. Objetivo hoje vai ser dar uma repassada nesses conceitos pra quem nunca viu. Se você é um programador sênior certamente já sabe de tudo isso e certamente já usa no seu dia a dia. Eu não chamaria ninguém que não segue no mínimo os 12 fatores de sênior. E eu digo o mínimo porque isso é o básico do básico e na parte final do video vou até complementar mais algumas coisas que todo mundo já deveria saber.</p>
<p>Os 12 fatores em si vou falar no próximo video. Pra entender os 12 fatores você precisa saber o que é Heroku. Se nunca usou Heroku faça um favor a você mesmo e veja qualquer tutorial básico de como subir um aplicativo nele. Pra um iniciante é uma experiência que muda sua forma de pensar em deploys. Pra quem é das antigas e nunca se atualizou também vai ser um choque. Quando precisava subir um projeto web pra produção, como a gente fazia nos anos 90 e começo dos anos 2000? A gente configurava um servidor remoto Linux, num Virtual Private Server ou VPS como um Linode da vida.</p>
<p>Nesse servidor a gente configurava tudo manualmente via telnet ou SSH. Instalava um banco de dados como MySQL, um servidor web como Apache, alguma linguagem interpretada como Perl ou PHP, copiava os arquivos da aplicação, normalmente feita em Perl ou PHP, via FTP ou hoje em dia SFTP e pronto, tava tudo no ar. Essa stack inclusive tinha um nome, chamado LAMP, que é acrônimo pra Linux, Apache, MySQL e Perl ou PHP. Isso é tecnologia do fim dos anos 90. Entenda, se alguém fizer isso hoje em 2022, você tá parado no tempo mais de 20 anos. Ninguém em sã consciência atualiza código direto no servidor assim via FTP mais.</p>
<p>Eu já tinha dito no episódio do ingresso.com que o jeito errado é fazer tudo rodar numa única máquina, configurar tudo manualmente. Atualizar código direto lá é a pior forma possível de colocar uma aplicação no ar. Agora vamos ver como é o estado da arte, a melhor forma de fazer deploy tanto do ponto de vista de escalabilidade quanto de segurança. E pra isso eu vou seguir um tutorial oficial do próprio Heroku pra vocês verem como é absurdamente simples.</p>
<p>O tutorial começa com o básico. Você obviamente precisa ter uma conta criada no Heroku. Se ainda não fez isso, vai lá depois de assistir o video e cria a conta. Eles tem tutoriais pra quem usa Rails, Node, Java e muito mais, mas já que falei da stack LAMP, vamos ver como se sobe uma aplicação PHP. Vamos assumir que se você é de PHP já tem tudo instalado e obviamente usa Composer pra gerenciar suas dependências.</p>
<p>Se você for de outras linguagens como Python ou Elixir, não importa. Se nunca viu Heroku funcionando não interessa a linguagem, se atenha ao passo a passo e o raciocínio. Qualquer bom programador tem que ser capaz de no mínimo seguir o raciocínio mesmo se for em outra linguagem. Também vamos assumir que você já sabe usar minimamente Git. Se ainda não sabe, assistam meus vídeos sobre Git depois. E vocês se lembram que no video de Conhecimentos Básicos eu falo que saber Git é básico? Pois é, repito, é básico. Não tem como trabalhar em projetos modernos sem saber Git.</p>
<p>Agora, pra usar Heroku precisamos instalar a linha de comando do heroku. Praticamente tudo que vai precisar tem nessa linha de comando. Num archlinux da vida basta fazer um <code>yay -S heroku-cli</code>, num Ubuntu da vida dá pra instalar com Snap. Procure como instalar pra sua distro mas no final você deve ser capaz de abrir um terminal, digitar <code>heroku login</code> e vai abrir um navegador pra logar na conta que acabou de criar. E, claro, sempre habilite autenticação de duas etapas, é o mínimo.</p>
<p>No próximo passo ele vai mandar você fazer o clone de um projetinho de exemplo feito em PHP Symfony, que é um dos muitos frameworks web pra PHP. Symfony tem muita inspiração tanto em Rails quanto Django. Pra projetos novos recomendo usar Laravel, que tem uma comunidade mais ativa e um ecossistema que cresceu bem em torno dele. De qualquer forma, se você sabe Git, clonar um projeto é arroz com feijão. Tudo vai acontecer dentro do diretório desse projeto, então só dar <code>cd</code> pra ele.</p>
<p>Agora vamos subir essa aplicação de exemplo. Pra isso precisamos cadastrar uma nova aplicação no Heroku. Você pode subir quantas aplicações quiser na sua conta e pode começar com opções gratuitas. Depois pode transferir a propriedade das aplicações que subiu pra outra conta, como do seu cliente. Pra cadastrar é simples, do diretório do projeto, num terminal, só usar a linha de comando que instalamos e fazer <code>heroku create</code>. Se não disser que nome quer, ele vai inventar um aleatório. O nome em si não interessa tanto porque depois você vai registrar um domínio de verdade e apontar pra esse nome, o CNAME, então o usuário final mesmo nunca vai ver esse nome. Mas vai servir pra gente testar no domínio do Heroku.</p>
<p>Uma vez a aplicação registrada na sua conta, agora é só subir o código. Quando rodou o comando anterior, ele também criou um branch remoto no Git do seu projeto. Então agora é só subir o que tem na branch principal <code>main</code> que antigamente se chamava <code>master</code> pra esse remote chamado <code>heroku</code>. Relembrando, todos os remotes ficam no arquivo <code>.git/config</code>, dá um <code>cat</code> nele pra ver o conteúdo, olha o remote lá. Agora é só fazer <code>git push heroku main</code>. Olha o que vai acontecer no seu terminal.</p>
<p>Se você ainda não entendeu, o Heroku criou um repositório Git remoto associado com sua nova aplicação, esse é o remote. É como se fosse um projeto novo no GitHub que você vai dar push. Mas o Git tem uma funcionalidade que você pode configurar que ele detecte quando você faz um <code>push</code> e rodar algum script. No caso, o Heroku detectou que tá subindo código PHP e por isso vai automaticamente instalar a buildpack pra ter as ferramentas de PHP que vai precisar como o próprio interpretador PHP, o Composer, Apache, NGINX e tudo mais. O que o Heroku chama de buildpack é mais ou menos o que você chamaria de Dockerfile.</p>
<p>Aqui vale um adendo se você já usa Docker. Por que o Heroku reinventou a roda com esses buildpacks em vez de usar Dockerfiles? Na verdade é o contrário: o Heroku precede a invenção do Docker. Na realidade, muito do Docker foi inspirado no que o Heroku fez anos antes dele. O Heroku foi lançado por volta de 2008, o Docker foi lançado só em 2013. O objetivo da vida do Docker e tudo que saiu em torno de containers como Docker Compose, Docker Machine, Dokku, até Kubernetes, é conseguir imitar o que o Heroku fez antes de todo mundo.</p>
<p>Vamos considerar o que tá acontecendo nesse ponto. O comando <code>git push</code> que fizemos tá mandando o Heroku fazer o equivalente a criar uma imagem de Docker, é semelhante a um <code>docker build</code>. Nos servidores deles rodam diversos containers, que eles chamam de <code>dynos</code>. Tem de diversos tamanhos, a versão gratuita são dynos de 512 megabytes de RAM, se não me engano com uns 4 cores ou núcleos virtuais fraquinhos. Se precisar, dá pra subir pra versões de 1 giga até 14 giga de RAM se precisar muito.</p>
<p>Mas cuidado, a grande maioria das aplicações deveria conseguir rodar suficientemente bem em 512 mega de RAM, se precisa de mais que isso precisa ver se não tá com vazamento de memória, ou você programou muito porcamente e tá enchendo a memória de lixo. Trabalhar em um ambiente mais restrito do que sua máquina local é uma boa prática. Qualquer notebook hoje tem 8 giga ou mais de RAM e você muitas vezes nem percebe que sua aplicação tá usando muito mais memória do que deveria.</p>
<p>Seguindo o tutorial a próxima coisa que ele manda fazer é escalar a aplicação com o comando <code>heroku ps:scale web=1</code> que basicamente manda o Heroku subir sua aplicação num único dyno. Se mudar pra web igual a 2, ele vai subir dois dynos. O que significa isso? Vamos fazer uma conta de padeiro. Eu disse que o dyno gratuito tem uns 4 cores virtuais. Se eu configurar o apache pra pendurar um fork de processo por núcleo significa que consigo ter até 4 requisições simultâneas. Se subir 2 dynos eu posso ter até 8 requisições simultâneas. Simultâneo significa exatamente no mesmo instante. Nessa conta de padeiro estou considerando 1 requisição por processo, sem considerar opções de threads ou I/O assíncrono.</p>
<p>Digamos que a aplicação leve 100 milissegundos pra responder uma requisição. Então em 1 segundo daria pra responder até 10 requisições. Com 1 dyno então seria teoricamente possível responder até 40 requisições por segundo e com 2 dynos até 80 requisições por segundo, entenderam? Isso é teórico porque estou assumindo que cada core conseguiria responder 10 requisições todo segundo, mas depende se durante o processamento de cada requisição se não bloqueia algum recurso que outra requisição pode precisar, como banco de dados e coisas assim, por isso o tempo real pode variar bastante.</p>
<p>Containers, como Docker, como Dynos de Heroku, não são máquinas virtuais. Eu explico sobre isso no meu episódio sobre Devops. Se você não sabe a diferença recomendo que assista depois. Mas na prática só entenda que é uma forma de particionar os recursos da sua máquina e cada programa rodar isoladamente achando que está sozinho nessa máquina. Assim é possível particionar uma máquina real grandona em diversos containers menores, dividir os recursos, e te cobrar de uma forma mais fácil.</p>
<p>Mais do que isso, o Heroku já deixa muita coisa de infraestrutura preparada pra você. Por exemplo, eu expliquei que tem esse comando <code>ps:scale</code> que permite subir a imagem da sua aplicação em múltiplos dynos. Significa que você tem vários servidores web de pé ao mesmo tempo. Agora, quando um usuário digitar a URL pra sua aplicação ele não cai direto no servidor web da sua aplicação e sim num balanceador de carga, ou load balancer, proprietário do Heroku, que vai pegar as requisições e distribuir nos dynos que você tem de pé provavelmente usando uma estratégia como round robin.</p>
<p>Se você é iniciante isso pode parecer estranho. Quando sobe um processo de servidor web na sua máquina local, seja nginx, seja apache, seja um webpy de python, tomcat de java, puma de rails, vai ser um processo pendurado em uma porta. Toda vez que no seu navegador você carrega <code>localhost:3000</code> ele vai direto pra quem responde nessa porta 3000 e é isso. Mas quando você sobe vários containers, cada um com seu próprio servidor na porta 3000, cada um dos containers tem um IP interno próprio. Faz de conta, 172.16.0.10 e 172.16.0.11.</p>
<p>No seu navegador você não pode mais usar localhost porque não tem mais ninguém no ip local 127.0.0.1 na porta 3000, que seria o localhost. Quando você sobe containers de Docker localmente, ele cria uma rede virtual local. Cada container de Docker que sobe ganha um IP virtual privado e nele que o servidor web da sua aplicação vai se pendurar na porta 3000 por exemplo. Se achou confuso recomendo que estude e treine Docker na sua máquina local, em particular usando Docker Compose pra orquestar cada serviço num container separado.</p>
<p>Você pode naturalmente digitar direto o ip privado de um dos containers como 172.16.0.10:3000 e aí vai carregar sempre só desse container, mas o segundo container vai ficar lá parado sem trabalhar. Pra conseguir acessar os dois containers, em vez de acessar direto, pode subir um terceiro container, com um load balancer como o NGINX ou HAProxy ou vários outros. Nesse load balancer você configuraria uma regra dizendo, toda vez que alguém mandar uma requisição na porta 80 eu envio pra porta 3000 de um dos dois containers que tenho cadastrado.</p>
<p>Isso que se chama um proxy reverso. Digamos que o container do load balancer subiu com IP virtual 172.16.0.13. Agora você pode ir no navegador e digitar <code>172.16.0.13</code> que por default vai conectar na porta 80. O load balancer vai pegar essa requisição e mandar pra um dos dois containers web respondendo nas suas portas 3000. Pro navegador é transparente. Ele não tem idéia de quantos containers web tem por baixo, nem em que portas eles respondem de verdade, nem que IPs tem. Load balancer é tanto uma forma de distribuir requisições pra mais servidores web quanto anonimizar os IPs dos servidores para que os usuários não saibam quem são.</p>
<p>O objetivo do episódio não é ensinar redes, mas como tem muito iniciante assistindo achei melhor pelo menos dar o resumo do resumo. Pra saber mais procurem artigos sobre load balancer, em particular tentem fazer exatamente esse cenário que eu falei: subir com Docker Compose 2 ou mais containers com uma aplicação web qualquer e outro container com NGINX configurado como load balancer e veja na prática suas requisições sendo distribuídas pelos seus containers. Tem dezenas de tutoriais que você acha no Google pra fazer isso, só não ter preguiça de procurar, pra treinar qualquer um serve.</p>
<p>Falando nisso, voltando pro tutorial do Heroku, o próximo passo é ver se sua aplicação subiu direito e está respondendo como deveria. Pra isso você precisa conseguir ver os logs em tempo real. Se fosse uma aplicação local era só fazer tipo um <code>tail log/application.log</code> pra ficar monitorando o que entrar no log em tempo real. Tail é inglês pra rabo, e como o nome diz a gente fica seguindo o rabo, ou seja, o final do arquivo. Pro Heroku é parecido mas só usar a linha de comando deles fazendo <code>heroku logs --tail</code>. Lógico, o <code>--tail</code> é opcional, mas se usar vai manter o log aberto e tudo que for sendo logado vai aparecendo no seu terminal. De novo, se você tá acostumado a usar Linux isso é algo super comum.</p>
<p>Fizemos o clone da tal aplicação de exemplo mas nem vimos o que tem nele. O principal é entender que toda aplicação que sobe no Heroku precisa ter pelo menos um arquivo na raíz do projeto chamado <code>Procfile</code>. Nele definimos que no container de tipo <code>web</code> vai executar o binário executável do apache apontando pra pasta <code>web</code>. Isso é específico de cada framework. Se fosse um Rails o executável seria <code>bin/rails server</code> e assim por diante. É assim que o Heroku sabe o que é pra executar dentro do container. Se fosse um <code>Dockerfile</code> seria o equivalente ao parâmetro <code>CMD</code>.</p>
<p>Não vou mostrar isso hoje, mas além de web existem outros tipos de containers que você pode declarar como <code>queue</code> ou <code>job</code> se quiser montar imagens que sobem workers pra uma fila assíncrona, por exemplo. Eu expliquei um pouco sobre filas assíncronas no episódio do ingresso.com e no de concorrência e paralelismo. O importante é saber que Heroku não sobe só aplicações web. Além disso, o formato de arquivo <code>Procfile</code> meio que virou universal. No mundo Rails você pode usar uma ferramenta como o <code>foreman</code> que vai ler esse arquivo e localmente subir sua aplicação pra simular como rodaria no Heroku.</p>
<p>Você pode usar o foreman escrito em Ruby pra rodar localmente sua aplicação mesmo se for escrita em PHP, ou usar o node-foreman que faz a mesma coisa ou o goreman ou forego que são outros clones de foreman escritos em Go. Tanto faz no que é escrito porque só vai ler o que tem no arquivo <code>Procfile</code> e executar o que está lá. É uma boa prática testar localmente antes de subir pro Heroku ou outra plataforma que também suporte Procfiles.</p>
<p>O próximo passo do tutorial foi o que expliquei agora pouco. Como escalar sua aplicação subindo mais containers web. Só usar o comando <code>heroku ps:scale</code> e fazer <code>web=2</code> ou mais. Se quiser desligar a aplicação, tirar da web, só fazer igual a zero que vai desligar todos os containers. Você pode aumentar ou diminuir o número de containers manualmente com esse comando ou contratar um serviço de auto-escala que usa algumas estratégias pra fazer esse ajuste automaticamente dependendo da carga que sua aplicação tá recebendo.</p>
<p>Dizendo assim pode parecer que escalar sua aplicação é algo tão simples quanto rodar esse comando e subir 100 dynos de uma só vez em horário de pico. Mas isso não é uma bala de prata. Lembre-se que se subir 100 dynos vai precisar que recursos embaixo dele, como seu banco de dados, tanto consiga aguentar esse tanto de conexões simultâneas e ter CPU e RAM suficientes pra processar tanta coisa de uma só vez.  Escalabilidade nunca é automática, você precisa estar preparado pra isso. Na prática, essa funcionalidade é mais pra você economizar custos. Digamos que no máximo, os recursos que instalou suportariam 100 dynos de pé ao mesmo tempo. Mas de madrugada seu tráfego cai um monte e só 10 dynos seriam suficientes. Então você pode fazer um script que derruba 90 dynos de madrugada pra economizar custos e de manhã cedo sobe 90 dynos novos pra aguentar o tráfego do dia.</p>
<p>Agora vamos falar de dependências. Se você programa em Rails, obrigatoriamente usa a ferramenta Bundler pra gerenciar dependências. Toda nova biblioteca que precisa adicionar no seu projeto, primeiro você declara no arquivo <code>Gemfile</code> e instala a tal biblioteca com o comando <code>bundle update</code>. Você jamais vai no site da biblioteca, baixa um zip e descompacta dentro do diretório do seu projeto, isso seria uma barbárie e algo totalmente inaceitável numa sociedade civilizada moderna em 2022.</p>
<p>Se você programa em Javascript, Node.js, obviamente adiciona todas as suas dependências com o comando <code>npm install</code> e a opção <code>--save-dev</code> por exemplo. Ou se usa <code>yarn</code> usa o comando <code>yarn add</code>. Em ambos os casos isso vai atualizar o arquivo <code>package.json</code>, que todo projeto civilizado de Javascript tem obrigação de ter.</p>
<p>Se você é de Java, certamente conhece Maven e usa a ferramenta Gradle, e tudo vai estar declarado no arquivo <code>build.gradle</code> ou <code>pom.xml</code>. Se você é de Python, eu sei que é chato e ainda não é uma solução perfeita, mas deveria estar usando <code>pip</code> e todas as dependências deveriam estar no arquivo <code>requirements.txt</code>.</p>
<p>Se é de Go, só recentemente começaram a ficar mais civilizados e agora fazendo <code>go mod init</code> você cria um arquivo <code>go.mod</code> que é onde se declara dependências. Daí rodando o comando <code>go mod tidy</code> vai baixar as dependências que precisa pra compilar e executar sua aplicação. O Rust sempre veio com o utilitário Cargo e você sempre tem um arquivo <code>Cargo.toml</code> que declara os crates, que é como povo de Rust chama suas bibliotecas.</p>
<p>Entenderam? Não importa que linguagem você usa, toda linguagem civilizada tem um gerenciador de dependências padrão, sempre tem um arquivo onde se declara essas dependências, e você nunca, jamais, deve baixar bibliotecas manualmente e jogar dentro do seu repositório. Mais do que isso, todo gerenciador competente costuma ter um arquivo de tranca, de lock. Por exemplo, projetos de Javascript tem o <code>package.json</code> onde você declarou as bibliotecas e versões que queria, mas quando o <code>npm</code> ou <code>yarn</code> realmente baixam e instalam ele gera um outro arquivo chamado <code>package-lock.json</code> que você nunca deve editar manualmente, que declara exatamente quais bibliotecas e exatamente quais versões baixou.</p>
<p>Esse arquivo de lock é especialmente importante porque quando outro desenvolvedor dá pull e clona o repositório do projeto ou quando você dá <code>git push</code> pro Heroku, ele roda o <code>npm install</code> e vai baixar exatamente o que estiver nesse arquivo de lock. 1.0.0 é diferente de 1.0.2. Isso é importante pra todo mundo baixar exatamente a mesma versão de tudo. Basta uma biblioteca que era pra ser, faz de conta, versão 1.0.1 e no servidor baixar a 1.0.2 que um bug pode ser introduzido sem ninguém saber. Gerenciamento de dependências é uma ciência exata. Ela só fica caótica quando você não segue essas regras.</p>
<p>Essa longa explicação foi só pra pular pro próximo passo do tutorial onde ele rapidamente explica que nosso projeto PHP de exemplo usa um gerenciador de dependências chamado Composer. E como esperado, existe um arquivo chamado <code>composer.json</code> na raiz do projeto junto com um arquivo <code>composer.lock</code>. No caso do PHP, no arquivo <code>web/index.php</code> tem uma linha com o comando <code>require</code> carregando um <code>autoload.php</code> que é quem se responsabiliza por carregar as bibliotecas declaradas no <code>composer.json</code>.</p>
<p>Na sua máquina local, se fizer <code>compose update</code> vai baixar todas as dependências localmente e agora pode rodar o projeto na sua máquina. E quando fazemos <code>git push</code> pro Heroku os scripts no buildpack de PHP vão procurar o arquivo <code>composer.json</code> e se achar, vai rodar o <code>compose update</code> pra montar a imagem. E é por isso que você precisa gerenciar as dependências dessa forma, pra que outro desenvolvedor não tenha problemas quando baixar na máquina dele, e pra quando subir num Heroku ou máquina de produção sabemos que não vai faltar nenhuma dependência. Isso é crucial pra vivermos numa sociedade civilizada. Imagina como era na época da barbárie quando não tínhamos ferramentas como essas.</p>
<p>Vamos simular que esse projeto ainda está em desenvolvimento. Vamos adicionar uma nova funcionalidade. Pra isso começamos usando o comando <code>compose require</code> que vai declarar e puxar a biblioteca <code>cowsayphp</code> que é um programinha besta que só desenha uma vaca com caracteres ASCII. Depois disso só rodar <code>compose update</code> pra garantir que foi baixada e instalada. Se abrirmos o arquivo <code>composer.json</code> veja que ela foi declarada automaticamente pelo Composer.</p>
<p>Agora, no <code>index.php</code> podemos criar uma rota chamada <code>/cowsay</code> que vai usar essa biblioteca <code>Cowsayphp</code> e mandar ela desenhar a vaca dizendo &ldquo;Cool beans&rdquo;. Vamos só copiar e colar esse trecho do tutorial no nosso projeto, e pronto. Agora precisamos adicionar tudo isso que modificamos no repositório Git. Pra isso basta fazer <code>git add .</code>. Lembrem-se que nos vídeos de Git eu falo pra tomar cuidado pra não adicionar coisas que não precisam. Por acaso esse projeto tem um <code>.gitignore</code> e se dermos <code>git status</code> podemos ver que só vamos adicionar os arquivos do Composer e o <code>index.php</code> que modificamos. Na dúvida sempre rode <code>git status</code> antes de dar <code>git commit</code>.</p>
<p>Finalizamos com o <code>git commit -m</code> adicionando uma mensagem descritiva da modificação que fizemos e executamos um novo <code>git push heroku main</code> pra subir a modificação e gerar uma nova imagem. Olhem o Heroku recebendo, rodando <code>compose update</code> que vai baixar a biblioteca que mandamos, vai regerar a imagem, daí ele sozinho vai desligar os dynos que estavam rodando antes e subir novos dynos com a imagem nova.</p>
<p>Se usarmos o comando <code>heroku open cowsay</code> ele vai abrir o navegador pra gente, já apontando pra URL da aplicação /cowsay. Não precisa desse comando, você podia abrir o navegador e digitar a URL manualmente, mas assim é mais rápido. Demora um segundo pra abrir porque o Heroku está atualizando os dynos, mas voilá, tá funcionando. E é assim que subimos versões novas da nossa aplicação. Lembram deploy contínuo que eu falei? É assim que faz.</p>
<p>Além de toda essa infraestrutura de containers, load balancers, facilidade de subir atualizações usando Git, o Heroku tem parceria com dezenas de empresas que oferecem serviços que são úteis pras nossas aplicações. No tutorial, o próximo passo é justamente instalar uma delas, o addon pra ferramenta chamada Papertrail. Addon é sinônimo de plugin e Papertrail é um serviço que recebe os logs da nossa aplicação e oferece uma interface web que podemos monitorar e, principalmente, fazer pesquisas.</p>
<p>A maioria dos addons oferece uma versão gratuita pra gente testar e pra aplicações pequenas costuma ser suficiente também. Pra instalar basta ir no terminal e fazer <code>heroku addons:create papertrail</code>. Esses comandos têm várias opções, por exemplo pra já instalar com um plano pago mais parrudo. Sempre leia a documentação de cada addon antes de sair instalando. De qualquer forma, com o comando <code>heroku addons</code> podemos listar quais já temos instalado e veja como o Papertrail já aparece.</p>
<p>Pra abrir a interface web no seu navegador, via terminal podemos rodar <code>heroku addons:open papertrail</code> e voilá, agora temos como fazer pesquisas nos nossos logs. Isso é mais importante se considerarmos que podemos ter mais de um dyno rodando ao mesmo tempo e no Papertrail vai concentrar os logs de todos os dynos ativos. Assim podemos pesquisar os logs de todos ao mesmo tempo. Por acaso esse é um addon que eu recomendo sempre instalar em toda aplicação. Papertrail e também o Rollbar que você pode configurar pra te notificar por e-mail se alguma mensagem de erro crítico aparecer no log.</p>
<p>O próximo passo do tutorial é um pouco mais avançado e eu não recomendo que você use isso se não for um desenvolvedor mais experiente. A linha de comando do Heroku permite abrir um container novo pra onde ele vai abrir uma conexão SSH segura. Dentro dele você pode rodar o shell interativo do seu framework como o <code>php -a</code> no caso de PHP ou o <code>rails console</code> no caso de Rails ou simplesmente abrir um shell bash caso queira checar alguma coisa no nível do sistema operacional. Tudo que carrega nos containers de web também carrega nesse container de console, então pode ser bom pra debugar algum bug que não acontece na máquina de desenvolvimento mas aparece quando sobe a aplicação no Heroku. Use com cuidado, mas essa opção já salvou minha vida diversas vezes.</p>
<p>Esclarecendo, esse comando não abre SSH direto pra um dos containers web rodando. Ele abre um novo container, com a mesma imagem que sobe nos containers web, só isso. A vantagem é que de lá você tem os mesmos acessos ao banco de dados se precisar muito rodar alguma query de emergência ou algo assim. E justamente por isso eu falo pra tomar muito cuidado, porque o que você rodar no banco vai ser permanente. Quando desconecta do shell interativo, esse container é destruído, por isso não crie ou baixe arquivos pra lá porque esse container vai sumir tão logo você se desconecte dele. É especificamente pra tarefas administrativas especiais.</p>
<p>Outro conceito que pra iniciantes pode não ser muito óbvio são variáveis de ambiente. Por exemplo, imagino que a maioria que usa Linux no mínimo já lidou com variáveis como <code>PATH</code> num arquivo local como <code>.profile</code> ou <code>.bashrc</code> da vida. Você faz alguma coisa como <code>export ABC=blabla</code>. Isso declara uma variável global na sua sessão e você pode ver o conteúdo da variável fazendo <code>echo $ABC</code> e vai imprimir o <code>blabla</code> no seu terminal.</p>
<p>Pois bem, é considerado uma boa prática declarar configurações assim como variáveis de ambiente, particularmente em containers. Se você já viu um arquivo <code>.env</code> na raíz do seu projeto, ele serve pra declarar e simular variáveis de ambiente do projeto. De novo, a gente inventou isso no Rails e todo framework web meio que adotou a mesma funcionalidade. Fazemos a configuração num arquivo pra cada desenvolvedor não precisar manualmente ficar escrevendo um monte de <code>exports</code> no seu profile local de Bash. Além disso é boa prática ter um arquivo como <code>.env.example</code> que quando clonamos o projeto fazemos uma cópia dele pra <code>.env</code>, assim não precisamos adivinhar quais variáveis existem pra usar.</p>
<p>E mais importante, é boa prática colocar <code>.env</code> no arquivo <code>.gitignore</code> pra nunca entrar no repositório Git. Isso porque os frameworks que suportam essa convenção, se existir o arquivo <code>.env</code> ele tem prioridade sobre as variáveis de ambiente de verdade configuradas no seu sistema operacional. Mas quando subimos pra produção não queremos que a aplicação carregue desse arquivo e sim das variáveis de verdade no sistema operacional. Então sempre se lembre dessa regra: <code>.env</code> sempre declarado no <code>.gitignore</code>.</p>
<p>Entendido isso, localmente na sua máquina de desenvolvimento você edita o que precisa no arquivo <code>.env</code> que só existe na sua máquina, mas em produção configura variáveis de verdade. No Heroku fazemos isso com o comando <code>heroku config:set</code>. Vamos fazer um exemplo pra ilustrar isso. Naquele arquivo <code>index.php</code> vamos adicionar mais uma rota, no caso substituir a rota raiz do site. Vamos repetir a palavra &ldquo;Hello&rdquo; X vezes, e essas X vezes vai estar declarado na variável de ambiente chamada <code>TIMES</code>. Pra ler essa variável, em PHP, se usa a função <code>getenv</code>.</p>
<p>Agora, não temos como editar um <code>export</code> no arquivo de profile de um dyno, pra isso usamos a linha de comando <code>heroku config:set TIMES=20</code>. Pra listar todas as variáveis que existem só usar o comando <code>heroku config</code>. Olhem como tem uma variável do Papertrail que foi criado pela linha de comando que usamos pra adicionar o addon, esse é o token secreto de acesso. Não tem problema ter coisas como tokens e senhas em variáveis de ambiente, porque elas só existem dentro do container. Se alguém conseguiu ver esse token é porque ganhou acesso ao seu container de produção e seu problema é bem maior do que só ter o token exposto, você está com tudo exposto. Ninguém jamais deve ter acesso direto aos containers.</p>
<p>E pra reforçar, é exatamente por isso que nunca se deve adicionar arquivos como <code>.env</code> no repositório Git, porque normalmente guardamos coisas como senhas e tokens em variáveis de ambiente, e esse tipo de informação, jamais, sob nenhuma hipótese, pode aparecer dentro de um repositório Git. Nenhuma credencial ou segredo jamais deve estar no código fonte do projeto. Tem que ser um tosco de proporções bíblicas pra pensar em colocar segredos num versionador de código que todo mundo tem acesso.</p>
<p>E pronto. Agora podemos fazer <code>git add .</code>, <code>git commit -m</code> e uma mensagem e finalmente <code>git push heroku main</code> de novo. Ele vai criar uma nova imagem com as modificações que acabamos de fazer e se abrirmos o navegador com <code>heroku open</code>, esperamos um segundo pro Heroku derrubar os dynos e subir com a imagem nova e, voilá, veja &ldquo;Hello&rdquo; repetido 20 vezes.</p>
<p>E como mencionamos “banco de dados” algumas vezes, o último passo do tutorial é justamente adicionar Postgres na nossa aplicação. Assim como o Papertrail, Postgres é um addon que tem diversos tamanhos e preços e você deve checar as opções antes de adicionar. Isso porque se pegar um muito pequeno e precisar dar upgrade, o processo não é simples e muito menos trivial. Leia a documentação do Heroku sobre isso. Uma das melhores coisas do Heroku é justamente o serviço de banco de dados, que é um dos melhores que existem, mas você precisa ter um mínimo de noção do que tá fazendo. Eles não fazem mágica nem são à prova de idiotas.</p>
<p>Avisos dados, pra adicionar a versão gratuita menor, que se chama <code>hobby-dev</code>, basta digitar no terminal <code>heroku addons:create heroku-postgresql:hobby-dev</code>. Agora vamos adicionar uma funcionalidade no nosso projeto PHP pra conectar no banco e listar o conteúdo de uma tabela em HTML, o arroz com feijão só. Pra isso começamos usando o Composer pra adicionar a biblioteca de PDO que é PHP Data Objects. Se você é de Java ou .NET da vida, é a mesma coisa que um DAO da vida.</p>
<p>Fazemos <code>compose require csanquer/pdo-service-provider=~1.1dev</code> que vai baixar uma versão compatível mas não necessariamente exata com 1.1dev dessa biblioteca, lembram? Por isso precisamos ter um arquivo de lock que vai registrar a versão exata que ele achou e baixou. Rodamos um <code>compose update</code> pra garantir que baixou tudo e agora podemos alterar o arquivo <code>index.php</code> de novo pra configurar o acesso ao banco de dados. Vamos copiar e colocar do tutorial.</p>
<p>O importante é saber que é uma convenção do Heroku ter a URL de acesso ao banco declarado numa variável de ambiente chamada <code>DATABASE_URL</code>. Podemos ver ela usando o comando <code>heroku config</code> e olha só. E esse trecho de código que copiamos e colamos é pra pegar o conteúdo dessa variável com a função <code>getenv</code> e parsear os diversos componentes dessa string e passar pra biblioteca de PDO configurar coisas como username, password, host, porta e path pra conseguir conectar no novo banco de dados Postgres. E simples assim, subimos um banco de dados seguro e funcional na nossa infraestrutura.</p>
<p>Agora vamos criar uma nova rota lá embaixo chamada &lsquo;/db&rsquo;. Ele vai usar esse PDO pra mandar uma query pro banco e fazer um loop com <code>while</code> pra ir montando um array com todos os nomes que voltaram da query. E pra montar o HTML, o framework Symfony oferece um sistema de template chamado Twig. Passamos o array de nomes pro template chamado de <code>database.twig</code>.</p>
<p>Então precisamos criar um novo arquivo <code>views/database.twig</code> e vamos copiar e colar o template do tutorial nele. Ele vai pegar cada nome que veio no array que passamos pra ele e montar uma lista em HTML, nada de mais, arroz com feijão. Isso tudo feito, vamos adicionar as modificações no Git com <code>git add .</code>. Com <code>git status</code> podemos ver que modificamos os arquivos do Composer, o <code>index.php</code> e criamos um arquivo novo <code>database.twig</code>. Está correto então podemos fazer <code>git commit -m</code> com uma mensagem descritiva e finalmente <code>git push heroku main</code> pra mandar as modificações pro Heroku.</p>
<p>Espero que a essa altura você já esteja acostumado com o processo de criar uma nova funcionalidade, adicionar no Git, mandar pro Heroku e conseguir testar de verdade. O Heroku recebe a modificação, monta uma nova imagem, derruba os dynos que estavam rodando e sobe de novo com a nova imagem. Só tem um probleminha.</p>
<p>Note que criamos um novo banco de dados, mas ele está vazio. Em qualquer framework web moderno existe um recurso chamado Migrations, mais uma coisa que nasceu no Ruby on Rails e todos os outros frameworks imitaram, onde criamos scripts que vão criar as tabelas e índices que precisamos, caso já não existam, e também já pré-cadastram coisas como conta de administrador, se precisar. Isso tudo fica em scripts que declaramos pro Heroku executar na próxima vez que subir dynos novos. Procure a documentação do seu framework e aprenda sobre Migrations porque é super importante.</p>
<p>Como isso é só um tutorial simples, tentando ser didático pra ensinar sobre o Heroku e não sobre seu framework, ele pula qualquer coisa sobre Migrations e manda você abrir um novo container de shell, lembra? Que nem abrimos o bash remoto? Só que no caso agora é abrindo o console do Postgresql que é o <code>psql</code>, então vamos fazer isso usando o comando <code>heroku pg:psql</code>.</p>
<p>Agora abriu o console remoto lá no servidor do Heroku já conectado no nosso novo banco de dados. Só precisamos fazer isso uma vez, mas vamos copiar do tutorial o comando pra criar a tabela chamada test_table e em seguida vamos dar insert em alguns valores aleatórios como esse &lsquo;hello database&rsquo; e também um &lsquo;hello world&rsquo;, por que não? Pronto, pra sair do console do Postgres é só digitar <code>\q</code>.</p>
<p>Podemos abrir o navegador de novo usando o comando <code>heroku open db</code> que é a nova rota que criamos e voilá, veja que conseguiu executar a query no banco, trazer as duas linhas que acabamos de inserir, e montar o HTML com elas. E pronto, nesse estágio fomos capazes de criar uma nova aplicação no Heroku, ir subindo novas funcionalidades à medida que fomos codificando, configurando o ambiente e cadastrando novos addons como o Papertrail e Postgres. Esse é o básico do básico sobre Heroku que todo mundo deveria saber. Mas o Heroku faz bem mais que isso. No fim do tutorial, no último passo, ele dá links pra outros artigos como o &ldquo;How Heroku Works&rdquo; que vai começar a explicar muito mais detalhes sobre o ciclo de vida de uma aplicação em dynos.</p>
<p>Por exemplo, lembram que durante o tutorial repetimos <code>git push heroku</code> várias vezes? Todas as vezes ele cria uma nova imagem com as últimas modificações, derruba os dynos rodando a versão antiga e sobe tudo de novo com a imagem nova. O que eu não expliquei é que as imagens antigas continuam disponíveis se precisar. O Heroku chama isso de releases ou lançamentos. Se rodar o comando <code>heroku releases</code> podemos ver tudo que subimos desde a primeira vez. Ele vai numerando cada release e acho que você pode cadastrar tags nelas também pra facilitar achar depois.</p>
<p>A vantagem mais imediata é: digamos que você subiu uma nova versão que não testou muito bem e, surpresa, começou a dar pau em produção. Em vez de ficar tentando consertar na tentativa e erro durante o sufoco, podemos rapidamente dar rollback pra última release que sabemos que tava funcionando, por exemplo <code>heroku releases:rollback v10</code>. A versão 11 que acabamos de subir dá pau, mas a versão 10 funcionava, então voltamos pra ela. É o tipo de coisa que você nunca ia conseguir fazer rápido e organizado assim se estivesse atualizando código fonte manualmente usando FTP. E é por isso que você paga mais por isso também.</p>
<p>Eu normalmente não gosto de fazer videos de tutoriais de ferramentas específicas porque elas envelhecem muito rápido. Mas assim como no caso de Git, o Heroku existe faz mais de dez anos e continua funcionando basicamente do mesmo jeito. Esse mesmo tutorial que funcionaria em 2010 continua funcionando em 2022 e provavelmente vai continuar funcionando por muitos mais anos do mesmo jeito e toda nova solução de devops que aparece é pra tentar se aproximar desse ideal de fluxo de trabalho. Por isso, mesmo que você não use no seu trabalho atual, acho muito importante que se familiarize com esse jeito de trabalhar.</p>
<p>No próximo episódio vamos pegar o que aprendemos hoje pra discutir um pouco mais sobre arquitetura e gerenciamento de projetos ágeis de verdade. Se ficaram com dúvidas mandem nos comentários abaixo, se curtiram o video deixem um joinha, assinem o canal e compartilhem o video com seus amigos. A gente se vê, até mais!</p>
]]></content:encoded><category>devops</category><category>cloud</category><category>desenvolvimento-web</category><category>akitando</category></item><item><title>[Akitando] #110 - Como fazer o Ingresso.com escalar? | Conceitos Intermediários de Web</title><link>https://akitaonrails.github.io/2021/12/08/akitando-110-como-fazer-o-ingresso-com-escalar-conceitos-intermediarios-de-web/</link><guid isPermaLink="true">https://akitaonrails.github.io/2021/12/08/akitando-110-como-fazer-o-ingresso-com-escalar-conceitos-intermediarios-de-web/</guid><pubDate>Wed, 08 Dec 2021 11:33:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/0TMr8rsmU-k"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;h2&gt;DESCRIÇÃO&lt;span class="hx:absolute hx:-mt-20" id="descrição"&gt;&lt;/span&gt;
&lt;a href="#descri%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Por que sites como Ingresso.com baleiaram nas vendas da pré-estréia do Homem Aranha? Hoje finalmente vou responder com técnicas de verdade como se faz pra suportar sites de grandes volumes de acessos e transações.&lt;/p&gt;
&lt;p&gt;Vou aproveitar pra explicar rapidamente como cursos e tutoriais levam iniciantes a uma versão correta mas não-escalável, e vou explicar 3 grandes técnicas que sistemas de alta escalabilidade realmente usam no mundo real. Em particular vamos tentar imaginar qual seria a solução pra resolver o problema de escolha de assentos, que é o principal ofensor num site de venda de ingressos.&lt;/p&gt;</description><content:encoded><![CDATA[

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

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


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

<h3>Descrição no YouTube<span class="hx:absolute hx:-mt-20" id="descrição-no-youtube"></span>
    <a href="#descri%c3%a7%c3%a3o-no-youtube" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Este episódio vai testar a paciência de vocês. Desta vez quero explorar um pouco os diversos assuntos que são associados com a tal carreira de &ldquo;Front-End&rdquo; no mundo de desenvolvimento Web.</p>
<p>Se já começaram a estudar o assunto devem ter visto que existe uma infinidade de sopas de letrinhas, coisas como SASS, CSS, React, Vue, e muito mais. Por que existem tantas letrinhas assim? Quais escolher?</p>
<p>Obs: eu fico falando &ldquo;Webpacker&rdquo; o tempo todo mas na verdade é &ldquo;Webpack&rdquo; sorry :-)</p>
<p>Links:</p>
<ul>
<li>
<p>History of Web Fonts: (<a href="https://thehistoryoftheweb.com/web-fonts/"target="_blank" rel="noopener">https://thehistoryoftheweb.com/web-fonts/</a>)</p>
</li>
<li>
<p>A brief history of CSS until 2016 (<a href="https://www.w3.org/Style/CSS20/history.html"target="_blank" rel="noopener">https://www.w3.org/Style/CSS20/history.html</a>)</p>
</li>
<li>
<p>Responsive Web Design: Media Queries (<a href="http://blog.chrismaxwell.com/responsive-web-design-media-queries"target="_blank" rel="noopener">http://blog.chrismaxwell.com/responsive-web-design-media-queries</a>)</p>
</li>
<li>
<p>Front-End Developer Handbook 2018 (<a href="https://frontendmasters.com/books/front-end-handbook/2018/"target="_blank" rel="noopener">https://frontendmasters.com/books/front-end-handbook/2018/</a>)</p>
</li>
<li>
<p>Eloquent JavaScript (<a href="https://eloquentjavascript.net/"target="_blank" rel="noopener">https://eloquentjavascript.net/</a>)</p>
</li>
<li>
<p>Mastering Regular Expressions (<a href="http://shop.oreilly.com/product/9780596528126.do"target="_blank" rel="noopener">http://shop.oreilly.com/product/9780596528126.do</a>)</p>
</li>
<li>
<p>Juliana Negreiros (<a href="https://github.com/juunegreiros/utilities"target="_blank" rel="noopener">https://github.com/juunegreiros/utilities</a>) - página cheia de boas referências.</p>
</li>
</ul>
<h2>Script<span class="hx:absolute hx:-mt-20" id="script"></span>
    <a href="#script" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Olá pessoal, Fabio Akita</p>
<p>Estamos no quarto episódio desta série, semana passada começamos com algumas coisas básicas do Developer Roadmap onde eu discuti porque saber Git e aprender Linux de verdade são as duas coisas mais importantes pra começar. Mas eu me lembrei no caso de Linux que eu disse que tinha duas formas, você aprender do jeito difícil que é instalar e configurar uma distro como Arch Linux num ambiente virtualizado como Virtualbox. Ou aprender do jeito ultra difícil, que eu esqueci de mencionar no final do episódio, e que é instalando direto como sistema operacional principal da sua máquina! Sim, vai ser bem difícil se for sua única máquina e você só tiver acesso ao site do Arch Wiki pelo celular. Mas vai por mim, hoje tá bem fácil porque a gente fazia essas coisas nos anos 90 só confiando que tinha tudo certo nos disquetes e sem internet pra baixar alguma coisa caso tivesse esquecido, e Wikis sequer haviam sido inventados, aliás, Google não tinha sido inventado. Então considere que hoje tá muito fácil e instale Arch Linux como seu sistema operacional principal. Se conseguir fazer isso sozinho, você está no caminho certo.</p>
<p>No episódio de HOJE vamos falar sobre um dos assuntos mais desnecessariamente complicados da carreira de programador Web, o guarda-chuva que chamamos de Front-end. Porém, em vez de seguir o Roadmap, neste episódio vou fazer uma longa tangente hoje. Aliás, vai ser um dos meus episódios mais longos, então não deixem de dizer nos comentários se preferiam que eu tivesse quebrado esse episódio em duas partes ou se nesses casos está ok ser longo mesmo.</p>
<p>Se você está entrando hoje no mercado de front-ends vai esbarrar em dezenas de letrinhas, coisas como Sass, Vue, React, Angular, Ember, Bootstrap, Npm, Yarn, Webpacker, Flow, TypeScript, Dart, BEM, SMACSS, PWAs, SPAs, RAIL, e por aí vai. Eu entendo porque alguém que está iniciando os estudos agora vai ficar extremamente confuso sobre por onde começar. E como sempre o melhor jeito de entender como começar é do começo.</p>
<p>(&hellip;)</p>
<p>A tangente que eu me referi é que eu vou fazer o contrário da maioria dos tutoriais ou guias que falam de front-end. Em vez de dizer logo de cara o que eu acho que você devia estudar, eu pessoalmente acho mais fácil explicar porque aquelas sopas de letrinhas existem e daí vocês mesmos podem começar a chegar em algumas conclusões. A história do front-end é a própria história da Web e algumas coisas que você tem hoje só fazem sentido no contexto dos anos 90 ou dos anos 2000, sem saber disso você meio que tem que engolir como as coisas são sem entender porque. No fim do episódio eu espero que vocês tenham muito mais perguntas do que neste momento.</p>
<p>A estrutura mais simples que você precisa ter na cabeça é a seguinte: quando você abre um navegador e digita um endereço tipo https : // meusite.com.br ele vai fazer dezenas de operações mas no mínimo ele precisa primeiro traduzir o domínio num endereço IP, e quem faz esse trabalho inicial é o tal servidor de DNS ou Domain Name Server que você já deve ter ouvido pelo menos o nome, pra onde a configuração de rede do seu computador está apontando. DNS é como se fosse uma lista telefônica, traduzindo nomes pra números.</p>
<p>Agora é tarefa da tal rede, roteadores, provedores, conectar seu navegador a um servidor. A internet funciona porque todas as pontas falam o mesmo formato de mensagens e trafegam de uma determinada maneira, que é o que chamamos de protocolo, e o padrão que nos interessa é o famoso TCP/IP. Por agora só entenda que com o endereço ele encontra o servidor certo no mundo. Bem em resumo estamos falando de um programa ou aplicação que é um cliente de TCP ligando a um servidor TCP, que fica escutando no que chamamos de uma porta. Todo servidor web que serve páginas HTML é um programa que quando você carrega ele se liga a uma porta, que é representado por um número que vai de 1 a mais de 65 mil.</p>
<p>A convenção é que um servidor Web ouve conexões na porta 80. Um servidor SSH ouve na porta 22. Um servidor Web também escuta na na porta 443 quando recebe requisição de conexão segura via HTTPS, e muitos servidores de desenvolvimento como Node.js, Rails ou Java ouvem em portas como 3000 ou 5000 ou 8080, respectivamente. Não há nada de mágico nesses números, são arbitrários e foram escolhidos anos atrás e por isso é só uma convenção, mas na prática, qualquer número de porta serve, contanto que exista alguma coisa “escutando” nessa porta.</p>
<p>Nesse minuto de explicação eu simplifiquei bastante. Na faculdade você faria exercícios simples de escrever um programinha que escuta numa porta e outro que conecta nessa porta. É assim que começa a nascer a semente de um servidor web e de um navegador. Agora o que é o servidor Web mais simples de todos? Ele simplesmente lê um arquivo da máquina e serve esse arquivo. O endereço completo que você digita no navegador se chama URL ou Universal Resource Locator que é composto pelo protocolo, pelo domínio e pelo caminho de um arquivo, no caso mais simples. Então antigamente você veria endereços como esse <a href="http://sitedafaculdade.edu/~jose/arquivo.html"target="_blank" rel="noopener">http://sitedafaculdade.edu/~jose/arquivo.html</a></p>
<p>E esse til jose, se você estudou Linux como eu disse no episódio anterior, vai saber que é um atalho pro diretório /home/jose. E é assim que você faz o servidor web mais simples de todos e, como hoje em dia sabemos, o mais bugado em termos de segurança porque se ele traduz direto da URL pra um caminho de arquivos no seu sistema operacional, sem nenhuma checagem, você poderia ficar tentando achar maneiras de vasculhar todos os arquivos do usuário, mesmo os que ele achou que não seriam servidos publicamente. E fazíamos isso mesmo antigamente pra achar brechas.</p>
<p>E quando o navegador, que é um exemplo de um programa que é cliente de TCP, recebe esse arquivo.html agora ele traduz as marcações pra elementos visuais. E como existe o elemento que chamamos de LINK que é o <a href=”http://….> onde “A” significa anchor ou âncora e href é hypertext reference, nasce o conceito de hipertexto onde um texto possui links pra outros textos. No começo ninguém estava interessado em visual, simplesmente em disponibilizar textos linkando com outros textos. E assim nasce a tal World Wide Web ou WWW. É um conceito que nasceria naturalmente dados esses elementos que eu mencionei até agora, era inevitável.</p>
<p>No próximo passo alguém pensou, e se em vez de digitar o nome de um arquivo eu digitar o nome de um programa executável? Um binário que é um programa, que no caso do Windows são aqueles arquivos que terminam o nome com .exe, tipo um Word.exe ou Excel.exe em vez de um hipertexto cujo nome termina com a extensão .html?</p>
<p>Num navegador bem antigo não ia acontecer nada. Provavelmente poderia dar erro porque o servidor não saberia interpretar algo que não é texto. Mas aí alguém fez um dos primeiros servidores web reconhecer quando alguém pede um executável e em vez de só ler o arquivo e cuspir o conteúdo pro navegador, ele primeiro tenta rodar esse executável no servidor e cospe pro navegador o resultado dessa execução, que esperamos que seja um texto em HTML.</p>
<p>Ou seja, pro navegador não faz diferença se pedimos um executável ou um arquivo texto ao servidor, contanto que o que volte pra ele como resposta seja um conteúdo texto em HTML. E se considerarmos que pedir arquivos texto, que nunca mudam é algo estático, assim nasce a primeira geração de sites dinâmicos, ou seja, que dinamicamente podem mudar o resultado dependendo do que o executável faz. Isso começa com programas binários feitos em C mesmo, e o padrão foi nomeado como CGI ou Common Gateway Interface. Mas fazer C ficar cuspindo HTML é como usar um canhão pra matar uma mosca, mas felizmente havia outra linguagem mais simples em ambientes UNIX e Linux e melhor adaptada pra manipular cadeias de texto, ou strings, e foi assim que Perl se tornou a principal linguagem para fazer CGIs e foi assim que o mundo começou a conhecer mais sobre essa sub-linguagem que o Perl trazia chamada Regular Expressions ou REGEX pra manipular strings. Se você ainda não sabe o que é REGEX coloque na sua lista de coisas obrigatórias pra estudar.</p>
<p>E depois do surgimento de alguns servidores web como o da NCSA, ou da Netscape, dentre outros, um começou a despontar pela sua facilidade de uso e por ser open source, ou seja de código aberto, o famoso Apache, cujo nome eu sempre ouvi a história de que foi inspirado em “A Patch”, que traduz em português como “remendo” já que ele não deixa de ser um puxadinho de remendos de código que foram sendo adicionados, um patch de cada vez. Pra quem não sabe, você pode comparar duas versões do mesmo arquivo de código, um mais antigo e outro mais novo usando uma ferramenta de linha de comando que vem na maioria das distribuições Linux chamada “diff” pra tirar a “diferença” entre eles e colocar num arquivo de remendo ou “patch”, e usar outra ferramenta, a linha de comando “patch” pra aplicar essa diferença.</p>
<p>E falando em remendos de arquivos, uma das coisas mais irritantes da Web no começo dos anos 90 era que se eu quisesse fazer um site com 10 páginas e algum layout ou estilo visual, eu precisava copiar e colar o HTML inteiro de uma página pra outra, dez vezes e daí editar o conteúdo diferente entre cada uma. Se depois eu quisesse mudar o layout pra outro formato, eu precisava abrir as 10 páginas e mudar uma a uma. Originalmente a idéia não era que o HTML ditasse como o conteúdo seria visualmente estilizado, a idéia era que cada navegador estilizasse as páginas do jeito que quisesse. O motivo disso foi porque HTML foi inspirado em outras linguagens de marcação de conteúdo como SGML, voltados estruturar documentos governamentais que pudessem ser lidos por um computador. A função do HTML era marcar o que era um cabeçalho, um parágrafo, uma lista, e os perfis de impressão decidiam fonte, espaçamentos e tudo mais pra comportar esse conteúdo numa página de papel, e os navegadores é decidiriam como apresentar esse conteúdo num monitor, inclusive o usuário do navegador poderia customizar escolhendo que não queria o padrão de fontes Times New Roman e sim Arial e todas as páginas mostrariam Arial. Por isso se você viu exemplos de páginas do começo da web, ela eram todas bem sem sal, com fundo cinza, fontes pretas e links azuis, tudo meio igual.</p>
<p>Você já podia embutir um jeito rudimentar de forçar o controle de estilo direto no HTML com o atributo chamado style, mas foi com CSS que separamos o estilo do conteúdo e ganhamos mais controle sobre como o navegador pinta o visual na tela, coisas como escolher fontes, tamanhos, espaçamentos, alinhamentos, e muito mais. Isso foi no começo dos anos 90, até lá por 95 ou 96 quando CSS apareceu pela primeira vez. Nessa época você já tinha Netscape superando o antigo Mosaic, mas o Internet Explorer sairia na frente porque implementava melhor o primeiro CSS, ao contrário da Netscape. Foi quando surgiram páginas de teste como o famoso ACID pra checar a compatibilidade de CSS entre navegadores. Tem uma página com a história completa da novela em cima do CSS que estou deixando nos links na descrição abaixo.</p>
<p>Essa briga deu início à Guerra dos Navegadores, as ferozes batalhas entre a Microsoft e a Netscape pela hegemonia da Web com cada versão de um navegador tentando ser melhor, principalmente em termos visuais e de interatividade do que o anterior. Recomendo muito que você procure pelo documentário da Discovery, The True Story of the Internet onde o primeiro episódio narra em mais detalhes como foi essa guerra. Eu falei sobre isso num dos primeiros episódios desse canal quando dei dicas de séries e filmes pra programadores.</p>
<p>No fim dos anos 90, a Netscape estava perdendo a guerra e resolveu mudar seu modelo de negócio que envolvia vender o navegador, e abriu seu código fonte, criando a fundação Mozilla. O Opera apareceu também mostrando como redimensionar conteúdo pros dispositivos móveis da época como os Palms, Pocket PC e os primeiros dumb-fones de marcas famosas na época como Nokia ou Siemens, a primeira geração de pseudo-“smartphones” vamos assim dizer, isso antes ainda do surgimento da Blackberry e foi quando vimos o primeiro uso de media types, que evoluiriam pra media queries e viriam a se tornar oficiais só no CSS3 em 2012, mas os primeiros rascunhos datam desde pelo menos 1997. Se você não sabe o que são media types, lembre-se que layout de conteúdo era mais especializado para impressão em papel, pra editorar revistas, jornais ou livros. E tínhamos agora que adaptar para monitores de computador, mas além disso ainda havia outras formas de mostrar conteúdo. Imagine dispositivos tácteis como braille para cegos ou que geram som para surdos. Cada um deles é um “tipo de mídia” ou media types. E o Opera começou a popularizar o uso do atributo @media que evoluiria para as media queries que usamos hoje onde o CSS pergunta ao navegador que tipo de mídia estamos lidando, se é uma tela de celular, tela de tablet ou tela de computador.</p>
<p>Com o boom do Internet Explorer foi quando a Microsoft começou a embutir o máximo possível da Web no Windows, e com o Windows 98 vimos pela primeira vez a integração dessas tecnologias direto no sistema operacional, como a utilização de MIME-types, uma tecnologia que surgiu primeiro em e-mails para possibilitar embutir arquivos binários em e-mails que eram somente texto puro. MIME que significa Multipurpose Internet Email Extensions e que você conhece como pares que determinam o tipo de arquivo como text/html ou image/png ou video/mpeg. É uma forma mais avançada do que no Windows são as extensões de arquivo como .html ou .png ou .mpg.</p>
<p>O File Explorer foi mesclado ao Internet Explorer que possibilitava explorar os arquivos da máquina ou explorar os arquivos na Web. Hoje em dia todos os navegadores de arquivos usam MIME types. E isso foi um dos estopins para caracterizar prática monopolista da Microsoft em forçar as pessoas a usar Internet Explorer. A comissão européia na época forçou a Microsoft a deixar os fabricantes de computador escolher se deixariam pré-instalado o Netscape ou Opera. Mas o argumento da Microsoft era que era impossível separar o Internet Explorer do Windows porque ele era parte fundamental de como você navegava no sistema. Foi uma controvérsia enorme na época do Windows 98.</p>
<p>Voltando ao assunto dos servidores web, quando servidores como Apache se tornaram capazes de coisas como executar um script que manipulava o HTML antes de mandar para os navegadores eu poderia escrever o HTML do layout como se fosse um template, um modelo, e fazer só o conteúdo diferente daquelas 10 páginas separadas, e se precisasse mudar o layout, bastava mudar nesse template e todas as 10 automaticamente seriam modificadas.</p>
<p>Com esse conceito começamos a evoluir o mundo de escrever páginas de texto com hiperlink pra algo mais dinâmico e visualmente mais agradável do que textos em fontes Times New Roman ou Courier New com fundo cinza. A diferenciação passou a ser a customização gráfica e de design dos sites e começava a nascer a profissão de web designers que começou com designers gráficos de revista tentando entender como traduzir a linguagem visual de revistas e livros no formato digital da Web. A gente via muito design muito bizarro nessa época, eu diria que foi a época da esplendor exuberante barroco do design Web.</p>
<p>E não é só isso, dado que linguagens no servidor, seja em C ou Perl poderiam ter acesso a recursos como bancos de dados, de repente uma nova possibilidade se abre: aplicações Web em vez de só sites Web. E se eu pudesse fazer um formulário onde o usuário pudesse digitar seu cartão de crédito e enviar para esse script Perl no servidor e gravar no banco de dados? É como se inicia a semente para coisas como e-commerces. A Microsoft novamente não deixou passar a oportunidade e criou seu próprio servidor web, o IIS ou Internet Information Services que adicionou tecnologias proprietárias como o Frontpage Server Extensions que, junto com o programa Frontpage, permitir criar páginas dinâmicas, com validações complicadas de formulário, de maneira simples. Porém páginas feitas com essas extensões só podiam rodar em IIS e serem interpretados por Internet Explorer, aumentando ainda mais a pressão em cima de Apache ou Netscape. Você encontra resquícios disso em produtos atuais da Microsoft como o servidor Sharepoint.</p>
<p>Em paralelo ao Perl e os CGIs, nasceu outra forma de criar páginas dinâmicas com extensões embutidas diretamente dentro do núcleo do Apache, e seu principal proponente foi o PHP. Agora, em vez do servidor web chamar um programa externo pra gerar os HTMLs, o próprio servidor web ganhou a capacidade de interpretar lógica de programação pra cuspir HTML. A Microsoft seguiu lado a lado adicionando em 1996 a extensão de ASP no IIS. E então nasceu a guerra server-side entre PHP e ASP pela hegemonia do mundo servidor. Na sequência ainda ganhamos aberrações como Coldfusion, representando o pico do mau uso do então inovador XML não só como forma de representar dados em formato de tags, mas usar tags como linguagem de programação, inspirado pelo famigerado JSP no mundo J2EE.</p>
<p>Isso coincidiu com a evolução do Java no lado do servidor, e outra categoria de servidores Web, os application servers ou servidores de aplicação. Entenda que um servidor de aplicação é um servidor web, mas nem todo servidor Web como Apache ou NGINX de hoje é um servidor de aplicação. Servidores Web tem como principal função servir páginas HTML e servidores de aplicação tem como principal função executar programas que cospem HTML. Essa época também coincidiu com a abertura inicial de capital ou IPOs da Netscape e Yahoo que marcam o início da famosa Bolha da Internet do meio pro fim dos anos 90. Com a entrada de programadores mais experientes do que meros designers fazendo HTML, tecnologias que permitiam o tráfego de informações seguras como o SSL da Netscape, nasceram as primeiras aplicações Web como content management systems ou CMS como o histórico FileNet, o monstruoso Vignette que todo portal de conteúdo usava, versões open source como PHP Nuke, Mambo que viriam a inspirar a próxima geração como Drupal, Joomla, ou Zope em Python, que daria origem ao Plone, e finalmente o nascimento do Wordpress no começo dos anos 2000 que ajudaria a iniciar a era dos blogs.</p>
<p>Uma vez que começou a ficar claro que era possível programar aplicações interativas, controlar o visual se tornou mais e mais importante. A evolução do CSS1 pro CSS2 que inaugurou também a época das Web Fonts, e moveu os layouts ou estruturas de páginas de usar tabelas e células para a era Tableless e design fluido também viu o nascimento de outras 2 tecnologias fundamentais para a Web de hoje: Dynamic HTML ou DHTML com o HTML versão 4 e uma tecnologia inaugurada pela Mozilla na forma de Asynchronous Javascript ou AJAX. E seu principal garoto propaganda, o GMAIL que começou a ser desenvolvido em 2001 e foi lançado só no dia primeiro de abril de 2004, também inaugurando a era dos memes do dia da mentira.</p>
<p>O GMAIL foi a personificação de tudo que Bill Gates temia nos anos 90: que os navegadores Web se tornassem substitutos dos sistemas operacionais, no caso do Windows, na função de executar programas. Ele previu corretamente que sem padrões proprietários você não teria como segurar os usuários no Windows, porque eles poderiam rodar o mesmo programa num navegador como Netscape em qualquer sistema como MacOS ou distribuições Linux. O GMAIL foi provavelmente a primeira aplicação Web que substitui com sucesso um dos programas nativos mais importantes num sistema operacional: o cliente de e-mails como o Outlook da Microsoft. E isso ameaçava tudo, incluindo o monopólio sobre programas como o Office.</p>
<p>Neste ponto da história, estamos entrando em 2004. Em termos de tecnologias de front-end avançamos o HTML até a versão 4 que comporta coisas como Dynamic HTML e AJAX. Derivado do SGML tivemos o HTML mas também surgiria o XML para estruturar qualquer tipo de dados e como uma estrutura mais geral onde HTML, ou mais especificamente, XHTML seria um caso especial. HTML é especializado em estruturar conteúdo de texto. XML é especializado em estruturar dados, como cadastros. CSS versão 2.1 é o cascading style sheet, que passou a suportar web fonts, internacionalização, posicionamento relativo e absoluto que permite que os elementos na tela de ajustem dinamicamente de acordo com o tamanho total da tela, ao contrário do que fazíamos no meio dos anos 90 onde considerávamos um comprimento fixo de tela pra encaixar os elementos, especialmente dentro de tabelas e usando muito recorte de imagens porque CSS não era poderoso o suficiente para alguns designs gráficos. Além disso, é importante entender que CSS é uma linguagem declarativa, onde declaramos regras visuais que são aplicadas no HTML e essas regras são cascateadas onde uma regra herda as características da regra pai, numa hierarquia de regras. Aliás, tenham sempre em mente a estrutura de uma árvore ou hierarquia. Quase tudo na Web é baseado nessa estrutura. HTML são árvores de elementos. CSS são árvores de atributos de estilo. Sites são árvores de páginas uma linkando na outra. E agora que evoluímos de simples sites de conteúdo hipertexto, pra gerenciadores de conteúdo CMS, pra e-commerces e com o início da era de aplicativos Web como Gmail e redes sociais como Orkut, MySpace e Friendster, abriu-se uma nova fase de desenvolvimento.</p>
<p>Em paralelo, na era Java do começo dos anos 2000 popularizou-se o conceito de Frameworks, que mais do que linguagens fornecem uma estrutura pré-definida para ajudar a desenvolver aplicativos, fornecendo coisas como segurança, validação de dados, autenticação e autorização granular, templates visuais, e que determinavam meio que um “guia” de lugares onde você deveria colocar cada tipo diferente de código. Antes disso cada aplicativo como Joomla ou Drupal fazia tudo do zero. No mundo PHP começaram a surgir frameworks inspirados em J2EE ou Struts, como o Zend Framework. E finalmente abrimos a era do Ruby on Rails pro fim de 2004 e o agora lendário demo do blog em 15 minutos que David Hansson gravou no evento FISL de 2005 no Brasil, inaurando a era dos frameworks que herdam os conceitos de Agilidade inaugurado pelo Manifesto Ágil de 2001.</p>
<p>Por causa de Ruby on Rails começou a surgir a primeira geração de sub-frameworks Javascript e CSS. Até então escrever HTML ou CSS ou mesmo Javascript era algo trabalhoso, que não funcionava exatamente da mesma forma em todos os navegadores. Lembrando que estamos na época do famigerado Internet Explorer 6 do também famigerado Windows XP e do Netscape 7. Com isso em mente, alguém pensou: porque não unificar essas coisas? Por que não fazer um framework via Javascript pra homogeneizar e evitar que o programador tenha que fazer código específico para Internet Explorer ou Netscape? Daí surge a primeira geração de micro-frameworks especializados em visual do lado do navegador como Mootools, Dojo, o próprio JQuery, e os que o Ruby on Rails tornou popular na época, no caso o Prototype e o Scriptaculous. Scriptaculous que popularizou novas formas de elementos de interação como drag and drop de elementos no HTML, onde podíamos arrastar as coisas como podemos arrastar e-mails no inbox do GMAIL.</p>
<p>Pouco tempo depois, em 2007 surge uma nova revolução na área de design visual e usabilidade. Vemos o nascimento dos smartphones a partir do primeiro iPhone lançado em julho de 2007. Com ele temos o navegador Safari, que é uma evolução do navegador chamado Konqueror do KDE no Linux, que usava o núcleo KHTML que viria a se tornar o que conhecemos hoje como WebKit. Só que telas de celular tinham resolução muito menor do que um desktop. Em 2007 o mais comum eram monitores com resolução de 1024 pixels por 768, mas o iPhone tinha meros 320 por 480 pixels.</p>
<p>Uma coisa que se tornou popular nos anos 90 e que eu não falei ainda, foi uma forma de simplesmente não usar nada de HTML nem CSS nem Javascript. Pra tentar tomar a dianteira, empresas como a Microsoft não fizeram só componentes proprietários pro servidor como o Frontpage Server Extensions ou ASP. A Sun criou o Java e queria dar um jeito de colocar Java em tudo. O Java servia pra muitas coisas incluindo fazer aplicativos pra Desktop, como os malditos aplicativos de Imposto de Renda que você usa até hoje. Mas eles queriam dar um jeito de colocar Java nos navegadores, mas sendo uma linguagem geral ela virar um problema de segurança se de repente você rodasse Java no navegador e ele pudesse acessar seus arquivos. Por isso criou-se o conceito de sandboxing, um container que permite executar código Java com baixos privilégios ou seja, sem privilégios pra acessar arquivos ou a própria rede, ele devia rodar dentro desse container e assim nascem os aplets.</p>
<p>Quem dessa época não lembra do famigerado applet de demonstração que mostrava água se mexendo num tipo de animação que era impossível de fazer só com CSS ou mesmo com GIF animado. E seguindo nessa tendência a Microsoft criou o padrão ActiveX que era o mesmo que applets mas em vez de usar Java você programava em C++ ou mesmo Visual Basic 6. Felizmente Applets Java e ActiveX já não existem mais, mas a Macromedia também fez seu sandbox na forma do famigerado plugin Flash que trazia tecnologias realmente legais pra época como ilustrações vetoriais com curvas de Bezier que já era usado em programas como CorelDraw ou Adobe Illustrator para conseguir embutir ilustrações de melhor qualidade e tamanho muito menor do que imagens bitmap como JPEG ou GIF. E mais do que isso, adicionar o conceito de linha do tempo para animar essas ilustrações. Adicionando uma linguagem bem tosca e simples chamada ActionScript e você podia fazer coisas interativas como joguinhos. Todo mundo jogou alguma coisa em Flash no começo dos anos 2000.</p>
<p>Em 2019 o processador de um iPhone, o famoso A12 Bionic nos novos iPhone XS é quase tão potente quanto um processador Intel Core em um Macbook Pro. Um processador em um iPhone original de 2007 era um processador ARM11 de meros 412Mhz e tinha só meros 128 MEGAbytes de RAM. E a bateria tinha não mais que 1400 miliamperes, hoje celulares tem até 4000 miliamperes. Entre pouco processamento e pouca bateria, sem teclado, sem mouse, só com touch, rodar Flash era um exercício de futilidade. Os primeiros Androids queriam se destacar deixando rodar Flash mas rapidamente foi provado que isso só deixava celulares Android extremamente lentos e a bateria acabava num piscar de olhos. Flash já não tinha boa reputação por causa do tanto de bugs e principalmente buracos graves de segurança em sua programação porca. A gente dava um jeito de aguentar com CPUs mais potentes e muita RAM em desktops, mas em smartphones não tinha pra onde correr. A Apple se recusou a rodar Flash e não só isso demonizou o Flash publicamente na famosa carta aberta de Steve Jobs que selou o destino do fim do Flash.</p>
<p>Só que a partir de 2005 a única forma decente de ver videos do YouTube ou outros sites era via o plugin de Flash ou via o plugin de Silverlight que era tipo uma evolução do ActiveX da Microsoft, mas também não durou muito. Sem esses plugins não havia video. A Apple ajudou a evoluir o HTML para suportar playback de vídeo acelerado por hardware, e popularizou o envelope MP4 de vídeo com compressão H.264 e com isso nascia novas tags de vídeo pra HTML. Somado ao fim do Flash também aumentou a pressão por adicionar funcionalidades de animação também acelerado pela nova classe de GPUs e assim surgem coisas como o atual canvas controlado por CSS e WebGL.</p>
<p>A revolução dos smartphones, a queda da Sun que foi adquirida pela Oracle pondo também um ponto final em applets, as ingerências da Microsoft pós julgamento anti-truste nos anos 2000 desacelerando os desenvolvimentos do Internet Explorer e tecnologias como Silverlight. Tudo isso forçou a W3C e seu consórcio de empresas a acelerar uma coisa que já estava muitos anos atrasado: a evolução pro HTML5 e CSS3 que só aconteceu em 2014 onde alguns anos antes disso todos os navegadores, começando com o surgimento do Google Chrome em 2008 começaram a implementar uma funcionalidade de cada vez.</p>
<p>Ficamos de 2008 a 2015 num período muito chato de transição. Tínhamos que desenvolver Web em meio a HTML, CSS, Javascript estando no meio do caminho e mudando o tempo todo em cada nova versão de cada navegador. Era impossível fazer um site que funcionasse em todos os navegadores e em todos os dispositivos ao mesmo tempo com os padrões ainda não oficializados e cada navegador implementando partes da especificação. E por conta disso surgiram meta-linguagens pra nos blindar essa bagunça. Foi quando JQuery deixou de ser opcional e passou a ser uma necessidade pra lidar com Javascript e CSS. Com o fim do Flash, Javascript voltou a ser a única alternativa viável pra animação e interação, uma linguagem extremamente ruim que não via uma evolução significativa desde o começo dos anos 2000. Então começou a surgir uma gambiarra na forma de transpilers, linguagens que não são compiladas em instruções nativa de uma máquina real ou uma máquina virtual, mas que traduzem de uma linguagem para outra linguagem.</p>
<p>Novamente, o mundo Ruby on Rails veio tentar ajudar. O rubista Hamptom Caitlin escreveu o SASS ou Syntathically Awesome Stylesheets, usando Ruby. SASS é tipo um dialeto de CSS que permite estruturar melhor o CSS, dando funcionalidade de reusar código, e esconder detalhes específicos de navegadores. O Twitter resolveu criar sua própria versão e daí surgiu o LESS. SASS e LESS ficaram concorrendo por anos mas SASS é superior hoje em dia.</p>
<p>Também do mundo Ruby, em 2009 Jeremy Ashkenas escreveu um compilador em Ruby chamado Coffeescript. Uma nova linguagem cujo resultado da compilação era Javascript. Ele adicionou dezenas de conceitos de linguagens como Ruby e Python para criar uma nova linguagem muito mais agradável de trabalhar e que geraria a porcaria do Javascript por baixo. Isso ajudou muito a evitar termos que usar algo como JQuery ou ficar nós mesmos nos preocupando em adicionar código específico pra cada navegador. Hoje em dia todo mundo demoniza Coffeescript porque a sintaxe dele é um pouco agressivo e pouco ortodoxo, mas foi dele que saíram diversos conceitos como interpolação de strings, melhor controle sobre enumerators e iteradores, a agora famosa sintaxe de fat arrow pra funções anônimas e muito mais que foi incorporado ao Javascript versão ES6 que só foi sair em 2015, tornando o Coffescript obsoleto.</p>
<p>Mais do que só matar o Flash, o surgimento de smartphones com telas verticais e resoluções diferentes de um monitor de computador, também surgiu a necessidade de criar estilos que se adequassem automaticamente ao formato da tela e com o movimento de tableless, ou seja, parar de usar tabelas pra estruturar layout em HTML, e a consolidação de elementos flutuantes com espaçamento relativo do CSS2 transicionamos pro tal design responsivo que tanto se fala em hoje em dia. Layouts que automaticamente se adequam ao dispositivo e com o advento da interação via touch uma nova classe de pesquisa foi iniciada, o que hoje chamamos de UX ou usabilidade. Pense que até então o normal eram teclado e mouse, como criar telas interativas que só usam o dedo? Hoje em dia isso é um problema resolvido, mas principalmente entre 2009 e 2015 isso foi assunto de muita discussão.</p>
<p>E como o primeiro smartphone de verdade do mundo foi o iPhone da Apple, além das tecnologias ela trouxe outra coisa do seu DNA: seu estilo de design e linguagem de usabilidade. Se o fim dos anos 90 foi a época barroca do design Web, a Apple trouxe a linguagem de Bauhaus: o que hoje chamamos de design minimalista, que valoriza os espaços em branco entre elementos, linhas simples sem o escândalo de cores bregas e tudo desalinhado que se via em páginas de redes sociais como MySpace. E em cima dessa linguagem as empresas pararam de reinventar a roda em cada site e passaram a criar bibliotecas de estilo reusáveis e daí nascem os frameworks de CSS, escritos nas linguagens SASS e LESS que falei acima. O mais famoso deles sendo o Twitter Bootstrap. Por isso muitos sites parecem que foram feitos pelo mesmo designer, porque todo mundo usa o Bootstrap em vez de partir do zero e ficar reinventando a roda redesenhando o visual de botões e outros elementos. Daí criamos elementos novos com o hamburguinho pra menus, os Jumbotrons e Scrollspy que toda landing page usa.</p>
<p>Com tantos novos elementos de Javascript e CSS surgiu outra necessidade. Agora não era mais só escrever um arquivo de CSS, linkar no HTML e pronto. Passamos a reusar dezenas de bibliotecas como um JQuery da vida, código pra Google Analytics, frameworks de CSS gigantes, enfim muitos e muitos assets. Primeiro, precisávamos dar um jeito de baixar todos esses assets e bibliotecas nos nossos projetos sem ficar indo de site em site, clicando no link de download e descomprimindo zips manualmente nos diretórios. No mundo Perl já tinhamos coisas como o CPAN que permitia baixar bibliotecas de um servidor central. No mundo Ruby, depois de várias tentativas finalmente consolidamos no Bundler pra não só baixar as bibliotecas mas gerenciar corretamente as versões corretas que nossos projetos precisariam. E no mundo Javascript as coisas começaram meio devagar com o advento do NPM que só ficaria realmente usável quando concorrentes como YARN tentaram tomar seu lugar. Mas no mundo Javascript o NPM pelo menos se tornou o padrão de fato para gerenciar os assets no lado da máquina do desenvolvedor.</p>
<p>Mas só isso não é suficiente. Como eu expliquei no começo do episódio, um servidor web cospe HTML e todos esses assets pro navegador. E como agora colocarmos dezenas deles nos nossos projetos você devia imaginar que quanto mais arquivos e quanto maior eles forem, mais lento vai ser pro navegador baixar e renderizar tudo. E isso se tornou ainda mais óbvio nos primeiros smartphones. Hoje em dia temos alta velocidade em conexões 4G e já estamos próximos de ir pra 5G, mas em 2009 o normal ainda era 2G e poucos com 3G. Como otimizar esses assets pra ficarem menor? Alguém parou pra pensar: esses assets de Javascript e CSS são tudo texto. Nós programadores sabemos fazer texto ficar menor. Por exemplo, nós escrevemos código com tabulação, espaços, usamos nomes grandes e descritivos pra ficar mais fácil outro programador conseguir editar esse código. Mas um navegador não se importa com espaços em branco ou nomes descritivos, computadores sabem ler dados sem formatação. E espaços em branco ou nomes grandes ocupam espaço desnecessário. Mais do que isso, quanto mais arquivos separados tivermos, mas lento vai ser. Então porque não juntar vários arquivos num único arquivo e porque não filtrarmos e tirarmos fora espaços em branco e reduzir nomes de variáveis e funções ao menor tamanho possível, dentre outros truques? Esse é o conceito que chamamos de minificação, minificar textos humanamente legíveis em textos menores horrorosos mas que navegadores conseguem ler. E com tantas atividades agora pra chegar nos arquivos de HTML, CSS e Javascript finais otimizados, temos que transpilar de SASS pra CSS, de Coffeescript ou ES6 pra Javascript ES5 que na época era mais usado. É o que chamamos de um canos de processamento de assets ou Asset Pipeline como chamamos no Ruby on Rails onde o conceito nasceu. E no mundo Javascript surgiram coisas similares como Grunt, Gulp e hoje em dia Webpacker somado a ferramentas como Babel que fazem exatamente esse tipo de coisa. Por causa disso, todo novo framework que se preza tem algum tipo de integração com coisas como Webpacker pra ter esse pipeline de processamento. Isso ficou um pouco menos necessário desde que evoluímos pro padrão HTTP/2, mas isso é assunto pra outro dia.</p>
<p>Evoluindo em cima da herança do GMAIL, só AJAX não era mais suficiente e passamos a experimentar em tirar lógica dos servidores de aplicação e transferir o controle do estado da aplicação pra mais próximo da tela gráfica, e assim surgem os frameworks Javascript. Em vez de ficar controlando cada elemento da tela individualmente via Javascript e ficar controlando os estilos globais com CSS, por que organizar uma tela de uma aplicação direto só em seus menores elementos? Por exemplo, num Spotify em vez de falarmos em botões ou listas, podemos separar em coisas maiores como Playlist, Menu de Usuário, dashboard de playback, lista de resultados de pesquisa de música. São todos grandes “componentes” que interagem entre si. E cada um deles tem uma representação gráfica que depende de um estado. Por exemplo, no dashboard de playback, temos estados como tocando, pausado, shuffle ativado ou Não. E pra criar esses componentes e gerenciar os estados no lado do cliente, aproveitando tecnologias do HTML5 como web storage que permite guardar esse estado no navegador, surgem frameworks como Ember, Angular, React + Redux ou Vue.js. E assim também surge o conceito de uma única página que carrega toda essa aplicação que é composta de javascript e assets como CSS e imagens, sem precisar mudar de página pra atualizar entre cada estado. Pra fazer isso usamos Ajax por baixo e rodamos tudo numa única página e por isso temos o conceito que vocês já devem ter ouvido de SPAs ou Single Page Apps.</p>
<p>Isso aconteceu a partir do nascimento de aplicações mais complexas em interação e estado como o próprio Google Docs, Slack, Spotify e outros. E é por isso que hoje você tem esses frameworks. Na época que a Apple estava na dianteira desse tipo de aplicativos por conta do iCloud ela tinha um framework próprio chamado Sproutcore. Dali veio a semente que viria a se tornar Ember, que também saiu da comunidade Ruby on Rails e por isso Ember se parece um pouco em estrutura com o Rails. O Google tinha o antigo GWT que foi muito usado no mundo Java pra fazer aplicativos parecidos com o Gmail, mas depois criou o Angular e depois o Polymer que não deu muito certo em adoção. E com o crescimento do Facebook e necessidade de criar aplicativos como o Facebook Messenger, surgiu o React que torna mais veloz o desenho de partes da tela cujo estado foi modificado com seu Virtual DOM. Ou seja, em vez de ficarmos redesenhando explicitamente um elemento na tela, tipo mudar a cor de um botão pra vermelho, só mudamos o estado do botão e deixamos esse Virtual DOM redesenhar o que precisar na tela, de forma que podemos evitar redesenhar o que não se precisa e ao mesmo tempo otimizar a velocidade total do redesenho. E na tentativa de fazer concorrência e evoluir em cima da experiência do Angular e React, no mundo open source surgiu o Vue.js que começou a ganhar algum momento graças à sua inclusão como padrão no framework Laravel no mundo PHP.</p>
<p>E nos dias de hoje, com a evolução do Javascript ES6, a dominância do Chrome como navegador mais usado e a decadência do Internet Explorer, e também o surgimento de processadores multi-core mais rápidos e mais eficientes nos smartphones, com RAM mais barata que permite que desktops e dispositivos consigam ter 6GB ou 8GB ou mais de RAM, começou a surgir uma nova classe de aplicativos: os híbridos. Aplicativos de desktop ou de mobile que tecnicamente tem uma estrutura nativa que liga com o hardware como camera, touch, audio e muito mais mas são controlados por uma fina camada de Javascript e outras tecnologias Web como HTML5 e CSS3. Então podemos usar a mesma estrutura que descrevi acima pra fazer aplicativos desktop ou mobile como é o caso de coisas que usamos todo dia como o próprio Spotify ou Slack ou editores de código como Atom ou Visual Studio Code. E assim surgem também frameworks híbridos como o Electron que é uma forma de encapsular tecnologias do Chrome e abrir como se fosse um aplicativo nativo ou então React Native que torna possível você desenvolver um aplicativo simples pra iOS ou Android como se estivesse desenvolvendo um site web em React.</p>
<p>Caraca!! Eu sei que eu pincelei bem por cima de dezenas de assuntos e mais de 25 anos de história. Já disse em outros episódios que eu sinto que nasci na hora certa porque eu pude ver cada evento que eu narrei aqui acontecendo em tempo real. Isso me ajudou muito porque eu pude ir aprendendo cada nova tecnologia à medida que elas foram aparecendo. Algumas delas morreram já, como ASP clássico, Flash, Silverlight, Active X, Applets Java, Coffeescript, GWT, e ao mesmo tempo muitas novidades apareceram na forma dos atuais padrões HTML 5, CSS 3 e Javascript ES6 e as ferramentas por cima delas como React, SASS e assim por diante. Mas eu espero que ao ouvir sobre essa linha do tempo você consiga ter uma noção melhor de onde cada coisa se encaixa. E eu recomendo que você assista este episódio mais vezes, não agora, mas de tempos em tempos pra ver se o que eu estou falando vai fazendo mais sentido à medida que você vai estudando e treinando.</p>
<p>Sempre que tiver dúvidas sobre uma tecnologia comece do começo: pesquise sua história. A história vai te dizer, porque essa tecnologia existe? Que problemas ela foi desenhada pra resolver? Ela ainda resolve esse problema da melhor forma ou já surgiram substitutos? Ainda vale a pena aprender ou já tem coisa melhor na frente? Por onde anda o autor original da tecnologia? Ou a empresa que patrocinou essa tecnologia? São perguntas como essas que vão te dar o norte sobre o que estudar e como estudar.</p>
<p>Se eu fiz meu trabalho direito, você começou este episódio com dúvidas sobre o que estudar. E deve ter terminado este episódio com mais dúvidas ainda! E esse é o objetivo. Se você tem poucas perguntas, não está se esforçando o suficiente. Faça a pergunta errada e vai receber a resposta errada. Reveja seus conceitos, veja a lista de coisas que tem pra estudar, coloque na linha do tempo, veja quais problemas você vai resolver com quais ferramentas. Encontre problemas pra resolver e vá aprendendo dessa forma: na tentativa e erro. Quais problemas? Eu não disse no episódio anterior que você precisa aprender a fuçar os projetos open source que estão no Github? É por ali mesmo, você vai achar o tal clone do Instagram que alguém fez. Ele vai estar feito em Angular, que eu falei aqui. Você já entendeu pra que ele serve, procure um tutorial online sobre Angular. Daí vai ver que tem arquivos com extensão .scss que é SASS e agora você sabe o que é SASS e que deve ter algum pipeline, como Webpacker, então procure um tutorial de SASS e outro tutorial de Webpacker. E quando for rodar, você já sabe pelo menos em conceito a diferença de um servidor web e um servidor de aplicações, e se levou a sério meu episódio anterior você está estudando distribuições Linux, agora procure um tutorial de como instalar, configurar e subir um NGINX por exemplo.</p>
<p>Cada um desses assuntos não é pra levar anos pra aprender. Se você for devagar, gaste 1 mês em cada item. Aprenda os 20% que eu vivo falando. E aprenda treinando. Teoria e prática. A parte avançada vai aparecendo à medida que você realmente coloca a mão no código e faz rodar na sua máquina. Qualquer coisa antes disso é perda de tempo. E apesar de eu não gostar de recomendar diretamente sites de tutoriais ou cursos, vou deixar linkado alguns que conhecidos meus recomendaram, então não deixe de ver esses links na descrição do vídeo abaixo.</p>
<p>E por hoje é isso aí, o objetivo de hoje foi colocar as tecnologias de front-end dentro da história. Semana que vem quero tentar fazer a mesma coisa com tecnologias back-end e na seguinte finalmente um pouco sobre devops. Eu sei que vocês ficaram com dúvidas, e eu mesmo não sou o melhor programador de front-end, então mandem suas dúvidas nos comentários abaixo pra todo mundo poder ajudar a responder. Se curtiram mandem um joinha, compartilhem com seus amigos, assinem o canal e cliquem no sininho pra não perder os próximos episódios. A gente se vê, até mais pessoal!</p>
<p>Este episódio vai testar a paciência de vocês. Desta vez quero explorar um pouco os diversos assuntos que são associados com a tal carreira de &ldquo;Front-End&rdquo; no mundo de desenvolvimento Web.</p>
<p>Se já começaram a estudar o assunto devem ter visto que existe uma infinidade de sopas de letrinhas, coisas como SASS, CSS, React, Vue, e muito mais. Por que existem tantas letrinhas assim? Quais escolher?</p>
]]></content:encoded><category>frontend</category><category>desenvolvimento-web</category><category>javascript</category><category>akitando</category></item><item><title>[Akitando] #25 - Sua Linguagem É Especial? Parte 2 em 2001</title><link>https://akitaonrails.github.io/2018/11/08/akitando-25-sua-linguagem-e-especial-parte-2-em-2001/</link><guid isPermaLink="true">https://akitaonrails.github.io/2018/11/08/akitando-25-sua-linguagem-e-especial-parte-2-em-2001/</guid><pubDate>Thu, 08 Nov 2018 17:00:00 -0200</pubDate><description>&lt;p&gt;Disclaimer: esta série de posts são transcripts diretos dos scripts usados em cada video do canal &lt;a href="https://www.youtube.com/channel/UCib793mnUOhWymCh2VJKplQ"target="_blank" rel="noopener"&gt;Akitando&lt;/a&gt;. O texto tem erros de português mas é porque estou apenas publicando exatamente como foi usado pra gravar o video, perdoem os errinhos.&lt;/p&gt;
&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/O4CWVQLbi48"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;h3&gt;Descrição no YouTube&lt;span class="hx:absolute hx:-mt-20" id="descrição-no-youtube"&gt;&lt;/span&gt;
&lt;a href="#descri%c3%a7%c3%a3o-no-youtube" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Recapitulando, na série &amp;ldquo;Sua Linguagem NÃO é especial&amp;rdquo; expliquei porque tudo que você acha que é diferente e moderno na sua linguagem já existia décadas atrás.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Disclaimer: esta série de posts são transcripts diretos dos scripts usados em cada video do canal <a href="https://www.youtube.com/channel/UCib793mnUOhWymCh2VJKplQ"target="_blank" rel="noopener">Akitando</a>. O texto tem erros de português mas é porque estou apenas publicando exatamente como foi usado pra gravar o video, perdoem os errinhos.</p>


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

<h3>Descrição no YouTube<span class="hx:absolute hx:-mt-20" id="descrição-no-youtube"></span>
    <a href="#descri%c3%a7%c3%a3o-no-youtube" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Recapitulando, na série &ldquo;Sua Linguagem NÃO é especial&rdquo; expliquei porque tudo que você acha que é diferente e moderno na sua linguagem já existia décadas atrás.</p>
<p>No episódio anterioe eu comecei  a explorar um pouco o outro lado: ninguém disse que você não pode gostar da sua linguagem e, mais importante, dominá-la até o fim.</p>
<p>Veja o que eu fazia quando estava no começo da minha carreira e compare com o que você faz hoje em dia.</p>
<p>Em particular, veja como exploração sem limites pode levar você a antecipar tendências, sem querer.</p>
<p>Quer ver como fazíamos &ldquo;Ajax&rdquo; e &ldquo;NoSQL&rdquo; já em 2001?  :-D</p>
<p>Links de referência:</p>
<ul>
<li>ASP XML Framework (<a href="https://github.com/akitaonrails/ASP-Classic-XML-NoSQL"target="_blank" rel="noopener">https://github.com/akitaonrails/ASP-Classic-XML-NoSQL</a>)</li>
<li>Nested Set (<a href="https://en.wikipedia.org/wiki/Nested_set_model"target="_blank" rel="noopener">https://en.wikipedia.org/wiki/Nested_set_model</a>)</li>
</ul>
<h2>Script<span class="hx:absolute hx:-mt-20" id="script"></span>
    <a href="#script" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>No episódio passado eu mostrei um mini-framework que eu fiz com ASP clássico tentando imitar o que, na época, era o moderno ASP.NET Web Forms. Estamos falando de 2001.</p>
<p>Mas alguns meses antes desse framework eu fiz um código que - olhando hoje - é ao mesmo tempo assustador - porque o código é grande e bem feio - mas também surpreendente.</p>
<p>Lembram quando eu falei no episódio de Blockchain rapidamente sobre o Hype do NoSQL em 2010? E se eu te disser que eu já usava noSQL em 2001?</p>
<p>(&hellip;)</p>
<p>Estamos em 2001, como eu expliquei no episódio anterior eu estava desenvolvendo e dando manutenção em diversos sites esportivos de propriedade da PSN como os sites do Corinthians e Cruzeiro aqui no Brasil.</p>
<p>Além disso havia todo um Gerenciador de Conteúdo usado pelos jornalistas pra postar artigos e todo workflow de aprovação e publicação. Tinha área pra subir fotos, ferramentas pra montar pesquisas online tipo polls e muito mais.</p>
<p>Mas tinha 2 problemas que eu queria resolver. Um deles é que antigamente, tanto em ASP ou PHP a gente fazia o HTML misturado com o código. Se você já abriu o código fonte de um Wordpress, bom, era pior que isso.</p>
<p>Olha um exemplo disso, (aponta pro lado) HTML, dados, styles (já tinha CSS mas ele não era tão poderoso como hoje e pouca gente sabia usar direito ainda) e código tudo misturado. Era uma zona.</p>
<p>Uma coisa que tava começando a se popularizar na época era XML. Mas nem todo mundo entendia isso direito ainda. Muito menos no mundo ASP. Assim como hoje em dia pra vocês é tudo JSON, pra mim naquela época o quente era tudo XML.
Tecnologia funciona assim, você não sabe os limites, vai testando.</p>
<p>Então o primeiro problema que eu queria resolver era separar os dados do HTML. Eu não lembro se já existia o framework de Java Apache Coccoon, mas pra quem não sabe ele tinha exatamente esse conceito. Pra quem não sabe o que é XSL é o Extensible Style Language. Hoje XSL é uma família, por exemplo você talvez já tenha ouvido falar de XPATH? Faz parte.</p>
<p>Na época só existia o XSLT que é um padrão de transformação pra modificar um XML em outro XML. Então eu pensei, ora, um exemplo de XML é HTML ou mais especificamente o XHTML que a gente usava na época. Um HTML mais restrito e que é um XML válido. Pensei, porque não separar os dados puros em um XML estruturado e usar transformações XSLT pra converter em HTML?</p>
<p>E foi o que eu fiz. Já que todo site de evento esportivo era basicamente o mesmo template mas com dados diferentes, eu reduzi os dados de cada evento em XML e usava essas transformações pra gerar o HTML final a partir de um template XSLT.</p>
<p>Vou falar que o código que eu fiz ficou tão complicado que só de bater o olho hoje rapidamente nem eu consigo entender tudo que eu fiz. (risos) Quem me conhece sabe que eu costumo dizer que coisas como testes unitários não é uma coisa que você faz pros outros, é pra você mesmo de amanhã. E eu digo essas coisas por experiência, se eu pudesse voltar 17 anos no passado eu daria um tapa na orelha em mim mesmo.
Mas dá um desconto, em 2001, o assunto testes automatizados era meio novo até em Java. Só pra vocês terem uma idéia o site do JUnit só foi lançado 1 ano antes, em 2000.</p>
<p>De qualquer maneira eu meio que consegui fazer isso e pelo menos alguns eventos como Campeonato Paulista, Copa Libertadores, Copa Merconorte, Copa Mercosul, eu acho que consegui implementar nesse framework.</p>
<p>Novamente, eu fiz componentes VBScript encapsulados em Windows Script Components ou WSC. Eu criei componentes com nomes meio bizarros como TXMLSynchro,
TXMLFront, TXMLEvents,
TXMLLanguage e TXMLTranslation.</p>
<p>Ou seja, como eram portais de conteúdo internacionais pra América Latina e Estados Unidos, eu também tinha uma mini-engine de internacionalização. E suportando 2 bancos de dados, SQL Server e Oracle.</p>
<p>E porque tudo começa com T? Porque eu era desenvolvedor de Delphi alguns anos antes. E em Delphi todos os componentes começavam com a letra T e eu acabei pegando esse hábito de começar tudo com T. Em Delphi existia essa convenção porque tudo que definia um Tipo começava com T.</p>
<p>O componente principal TXMLSynchro usava uma convenção de pastas e ele ia percorrendo as pastas em busca dos XML e os templates XSL pra ir gerando os HTML. Esse conceito soa familiar? Se você já usou Jekyll por exemplo, é parecido, só que em vez de ser Markdown, era XML. Então eu tinha um gerador de site em XML em 2001.</p>
<p>Na verdade, era um passo além. Os jornalistas podiam editar o conteúdo via um Admin que gravava o XML no banco de dados. Daí eu pegava esse XML e aí convertia pra HTML via o XSL, por isso era TXML Synchro o nome do componente.</p>
<p>Ao mesmo tempo esse pacote era um gerador de eventos, daí o outro componente TXML Event, daí cada nova Copa ou evento era mais ou menos simples de gerar tudo.</p>
<p>Agora vem o principal. Eu gostei desse conceito de gravar XML natabela. E fui um passo além. Tinha um outro problema em todos os eventos esportivos, todo campeonato era visualizado via uma chave. Sabe? Basicamente uma árvore de nós convergindo na semi-final, final até o campeão.</p>
<p>Mas todo evento tem chaves diferentes. O problema: como eu represento isso num sistema multi-eventos?</p>
<p>A primeira coisa que você provavelmente teria vontade de fazer é criar uma tabela Evento, uma tabela Jogo e uma tabela Time ou algo assim e ir criando uma linha no banco pra cada nó da árvore. Isso é um design terrível. É o pattern que chamamos de Parent-Child. Terrível porque não tem como trazer todos os nós da árvore num único select.</p>
<p>Árvore é uma das coisas ruins de se mapear numa tabela SQL. O jeito correto é usar um Nested Set, vou deixar o link abaixo se você nunca ouviu falar em Nested Sets. Mas em 2001 eu também não conhecia isso, só conhecia o Parent-Child que é o que eu falei agora que é ruim, e pelo menos eu sabia disso.</p>
<p>Aqui foi um insight. Gerenciar nós de uma árvore, em SQL não é trivial. Gerenciar nós num XML era fácil, é um DOM, como o DOM que você manipula hoje via Javascript num HTML, é uma árvore. CreateElement, Sibling, Child, etc. Mas não existe banco de dados XML, não em 2001.</p>
<p>Mas, bancos SQL tem campo BLOB. E se eu simplesmente gravar o XML num campo blob? Daí com um único select eu puxo o XML inteiro que representa a árvore toda e posso passar num DOM Document pra parsear e criar os objetos todos.</p>
<p>E se eu for além, e se eu usar XSL em cima do XML que veio do banco pra gerar o HTML final que o jornalista ia ver no gerenciador de conteúdo?</p>
<p>E se eu for além, e se eu usar esse troço novo que o Internet Explorer 4 tinha inventado uns 3 anos antes chamado Dynamic HTML que me permite modificar o HTML dinamicamente na página via Javascript? Cês tão vendo? Eu to falando de tecnologia de ponta aqui.</p>
<p>E se eu somar isso a esse componente novo chamado MSXML que eu podia instalar tanto no servidor mas tinha componente pro browser. Isso permite, via Javascript ou VBScript, fazer uma requisição HTTP pra aplicação ASP me devolver o XML e, no client-side, eu poderia via javascript parsear com MSXML e ter o DOM na memória?</p>
<p>Agora se eu somar tudo isso. Eu posso fazer o jornalista clicar em qualquer nó da árvore, editar o nome do time, pontuação e tudo mais direto na representação do DOM do XML na memória do browser. Daí via Javascript gerar o XML que representa essa árvore e dar POST pro servidor, que daí ia gravar direto na tabela do banco.</p>
<p>O que eu tinha acabado de fazer?</p>
<p>Asynchronous Javascript and XML ou AJAX, nome que seria cunhado só em 2005, 4 anos depois. Eu não fui o primeiro a fazer isso, muito antes do Google fazer o Gmail que popularizou a técnica, a Microsoft já tinha o Outlook Web App em 2000 que era basicamente um Outlook versão Web que usava o componente XMLHTTP, que é o precursor do XMLHttpRequest. Ninguém usa esse objeto direto, hoje em dia vocês usam o que? Axios? Mas por baixo é o mesmo XMLHttpRequest. Esse componente nasceu pela primeira vez em 1997 ou 98 com o Internet Explorer 4, mas acho que só começou a ser usado de verdade com o IE 5 em 1999.</p>
<p>E isso de serializar e deserializar árvores de objetos XML num campo blob numa tabela é um tipo rudimentar de NoSQL. É o que hoje a gente faz com Postgres e o tipo de campo JSONB. É basicamente a mesma coisa: em vez de fazer um monte de tabela pra cada campeonato, eu reduzi tudo numa tabela só sem schema, schemaless.</p>
<p>O termo NoSQL foi cunhado primeiro em 1998 mas só se popularizou quando a Last.fm reintroduziu o termo em 2009, 8 anos depois do que eu fiz.</p>
<p>Imaginem minha surpresa quando em 2005 eu vi todo mundo ficando doido com o conceito de Ajax e em 2009 todo mundo ficando doido com o conceito de NoSQL. Eu juro que coçava a cabeça pensando, é legal, mas isso é velho não?</p>
<p>Lembrando que em 2001 não tinha ainda Hacker News, nem Reddit, nem Quora, nem Stackoveflow. Esses conceitos nasceram organicamente combinando as peças que eu aprendi fora da caixa e fazendo o que todo programador novo faz: over engineering.</p>
<p>Engraçado que eu tinha noção que a solução que eu fiz era bem overkill e over engineering mesmo pra época, mas assim como muitos de vocês, é aquelas coisas que você faz mais porque é divertido e menos porque é um bom custo-benefício.</p>
<p>O objetivo dessa história é ilustrar como sem querer eu cheguei nas mesmas conclusões que outras pessoas chegaram de outra forma. Acho que muitas outras pessoas usavam da mesma maneira, mas não era tão famoso porque não tinha blogs, não tinha meetups, não tinha screencasts nem nada disso em 2001. Se você não prestar tanta atenção na propaganda, ou a forma como os outros dizem que você deve usar essa ou aquela ferramenta e encarar tudo como peça de lego eventualmente você mesmo vai chegar nas mesmas conclusões que os grandes nomes chegam.</p>
<p>O que eu falo mais é o seguinte: se você está seguindo o que os outros estão dizendo, você já está atrasado. Eu não sigo muito os outros, eu penso: pra que esse troço serve ou não serve. Em vez de seguir o que os outros dizem “ah, isso só dá pra fazer se for em Java” ou “ah, isso o melhor é se for em Javascript”. Eu penso. Quem disse? Deixa eu testar. 99.9% das vezes não funciona, mas essa é justamente a graça do negócio. Programação não é pra ser um negócio que todo código que vc digita precisa necessariamente ser útil pra alguma coisa. É como dizer que toda vez que você for chutar pro gol precisa fazer gol. Senão nem vale a pena tentar chutar. Se futebol fosse só isso, não teria graça, teria? E fazendo assim, um vez por década você tem um momento eureka!</p>
<p>E você, já teve seu momento eureka em algum momento? Compartilhe com a gente sua história nos comentários abaixo. Se curtiram o vídeo mandem um joinha, assinem o canal e cliquem no sininho! A gente se vê, até mais!</p>
]]></content:encoded><category>desenvolvimento-web</category><category>arquitetura-de-software</category><category>linguagens-de-programacao</category><category>akitando</category></item><item><title>[Akitando] #24 - Sua Linguagem É Especial? Parte 1 em 2001</title><link>https://akitaonrails.github.io/2018/11/06/akitando-24-sua-linguagem-e-especial-parte-1-em-2001/</link><guid isPermaLink="true">https://akitaonrails.github.io/2018/11/06/akitando-24-sua-linguagem-e-especial-parte-1-em-2001/</guid><pubDate>Tue, 06 Nov 2018 17:00:00 -0200</pubDate><description>&lt;p&gt;Disclaimer: esta série de posts são transcripts diretos dos scripts usados em cada video do canal &lt;a href="https://www.youtube.com/channel/UCib793mnUOhWymCh2VJKplQ"target="_blank" rel="noopener"&gt;Akitando&lt;/a&gt;. O texto tem erros de português mas é porque estou apenas publicando exatamente como foi usado pra gravar o video, perdoem os errinhos.&lt;/p&gt;
&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/NwAvovzHRDU"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;h3&gt;Descrição no YouTube&lt;span class="hx:absolute hx:-mt-20" id="descrição-no-youtube"&gt;&lt;/span&gt;
&lt;a href="#descri%c3%a7%c3%a3o-no-youtube" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Muito bem, na série &amp;ldquo;Sua Linguagem NÃO é especial&amp;rdquo; expliquei porque tudo que você acha que é diferente e moderno na sua linguagem já existia décadas atrás.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Disclaimer: esta série de posts são transcripts diretos dos scripts usados em cada video do canal <a href="https://www.youtube.com/channel/UCib793mnUOhWymCh2VJKplQ"target="_blank" rel="noopener">Akitando</a>. O texto tem erros de português mas é porque estou apenas publicando exatamente como foi usado pra gravar o video, perdoem os errinhos.</p>


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

<h3>Descrição no YouTube<span class="hx:absolute hx:-mt-20" id="descrição-no-youtube"></span>
    <a href="#descri%c3%a7%c3%a3o-no-youtube" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Muito bem, na série &ldquo;Sua Linguagem NÃO é especial&rdquo; expliquei porque tudo que você acha que é diferente e moderno na sua linguagem já existia décadas atrás.</p>
<p>MAS, isso dito, nesta nova série eu quero explorar um pouco o outro lado: ninguém disse que você não pode gostar da sua linguagem e, mais importante, dominá-la até o fim.</p>
<p>Veja o que eu fazia quando estava no começo da minha carreira e compare com o que você faz hoje em dia.</p>
<p>Correção: Colo Colo é do Chile, eu falei errado no vídeo. Sorry.</p>
<p>Links de referência:</p>
<ul>
<li>ASP WebForms (<a href="https://github.com/akitaonrails/ASP_Classic_WebForm"target="_blank" rel="noopener">https://github.com/akitaonrails/ASP_Classic_WebForm</a>)</li>
</ul>
<h2>Script<span class="hx:absolute hx:-mt-20" id="script"></span>
    <a href="#script" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Sua Linguagem É Especial! Parte 1</p>
<p>Se você assistiu meus 2 vídeos intitulados &ldquo;Sua Linguagem NÃO É Especial&rdquo; que conta como muitas das funcionalidades que você achava que só sua linguagem tinha e que eram coisas modernas na verdade vem de linguagens de 30, 40 ou 50 anos atrás.</p>
<p>Dependendo de como você assistir talvez alguns me interpretem sendo meio niilista. Pode parecer que eu estou dizendo pra não se importar com as linguagens que você gosta. Ou não gostar de nenhuma linguagem.</p>
<p>Na verdade sua linguagem favorita É especial mesmo ela NÃO sendo especial.</p>
<p>(&hellip;)</p>
<p>Se você ficou confuso, lembre de qual era meu ponto nos vídeos anteriores: você não é definido pela sua linguagem.</p>
<p>Muita gente coloca suas linguagens e ferramentas num pedestal e agem até defensivamente, querendo esconder, minimizar ou desconversar até as falhas óbvias da sua linguagem ou ferramenta. “Ah, mas a outra linguagem também é ruim em X ou Y”.</p>
<p>Eu sou promíscuo. Eu Não tenho afiliações, eu não tenho lealdade de marcas - eu não sou patrocinado, porque eu defenderia com unhas e dentes, de graça, uma marca que não é minha? -, eu não devo nada a nenhuma empresa. Elas é que devem pra mim e precisam me convencer a usar. Nada que você usa de graça hoje é de graça, lembre-se, se você Não está pagando, o produto é você. Esse é o conceito de freemium.</p>
<p>Além disso existe outra verdade fundamental sobre tecnologia: ela se torna obsoleta muito fácil. Tenho pena de quem era evangelista de Blackberry, já pensou? Ou os tempos difíceis de ser um evangelista de Perl hoje? Onde estão os evangelistas de Solaris? Deve ser sofrido tentar defender o Delphi hoje.</p>
<p>Eu não. Meu maior prazer é aprender, mas muito mais dominar, e dissecar todas as coisas que eu gosto. E jogar fora no minuto em que não me servem mais. Porque alguém acharia útil limitar o alcance do próprio conhecimento e experiência?</p>
<p>A melhor coisa que você pode fazer pra sua carreira é não tomar lado, nunca ter uma posição. No minuto que você faz isso, você se torna vulnerável. Mantenha sua opções abertas. Você sabe que está vulnerável quando alguém fala alguma coisa ruim da sua ferramenta favorita e você se sente ofendido. Se sentir ofendido é uma opção.</p>
<p>Existe uma observação empírica chamada Difusão de Inovação, que é o gráfico de adoção de alguma inovação.</p>
<p>Só vale a pena ser um evangelista se você está entre os Early Adopters e Early Majority. Você não quer ser um evangelista na fase de Late Majority ou Laggard. Defender Delphi hoje é ser um Laggard. Defender iOS em 2007 é Early Adopter. Em 2011 já é Late Majority. Quanto mais no fim dessa curva, menos seu conhecimento tem valor. Entenda que valor de conhecimento é inversamente proporcional à adoção.</p>
<p>Isso dito, deixa eu voltar ao ponto: sua linguagem é especial. Claro que é, como eu mostrei nos vídeos anteriores, é o resultado de uma longa linhagem de linguagens e pesquisas anteriores que culminaram de alguma forma no que você está usando hoje.</p>
<p>O erro é achar que ela vai continuar sendo o estado da arte pra sempre. Claro que não. Em breve seja lá qual linguagem você está usando vai se tornar o próximo Cobol ou Smalltalk.</p>
<p>Neste vídeo e no próximo quero contar duas histórias que aconteceram comigo quando eu tinha 24 anos, pra dar uma perspectiva pra vocês que estão nessa mesma faixa agora.</p>
<p>Se você está no começo de carreira, digamos que você usou Express, ou Django, ou Laravel, ou Rails. Você já não sentiu vontade de escrever seu próprio framework? Claro que sim, todo mundo já passou por isso.</p>
<p>Em 2001, dentre outras coisas, acho que já tinha uns 2 anos que eu usava ASP. Não o ASP.NET, o ASP clássico mesmo. Eu estava trabalhando numa ponto com, a Panamerican Sports Network, ou PSN. Ela fazia parte do grupo Hicks Muse Tate and Furst. Era dona de diversas equipes esportivas na América Latina como Cruzeiro, Corinthians, Figueirense, Colo colo da Colombia, Tigres e Chivas-Rajadas do Mexico, patrocinava o Alan Prost na Formula 1, tinha contratado o Pelé pra um programa de TV - eu lembro de ter ido na festa de lançamento - e no meu escritório em SP que tinha basicamente jornalistas, eles tinham lá colunistas como Sócrates e o Marcelo Fromer.
Eu falei, a bolha das ponto coms era absurda em torneira aberta de dinheiro.</p>
<p>Eu trabalhava na construção de todos esses sites com uma equipe que ficava na Flórida e alguns outros espalhados em outros países como Argentina, e acho que Venezuela. Nesse meio do caminho, a Microsoft estava se preparando pra atacar a dominância do Java. Eu estava muito empolgado com o lançamento do .NET na época. Mas ainda era meio beta. E naquela época as coisas não saíam tão rápido quanto saem hoje em dia.</p>
<p>O ASP que hoje chamamos de clássico, na época era o padrão de fato e de direito. Ele não iria embora por alguns anos ainda mesmo com o .NET. O .NET pra web tinha um framework novo, o ASP.NET Web Forms.</p>
<p>Comparado com o que se tinha no ASP clássico, PHP e o JSP da época, era uma evolução significativa. Em resumo, tinha 2 coisas que eu gostei, na época. A primeira era o próprio Web Forms. Em resumo, ele mantinha uma estrutura de dados que representava o estado da página e toda vez que você dava um POST ele mandava esse estado de volta pro servidor. O Estado ficava num campo hidden escondido no form encodado em Base64. Então a gente manipulava o estado nos callbacks como se fossem atributos num objeto que era serializado e desserializado entre o HTML e o C# e o Web Forms renderizava o HTML e conservava o estado escondido.</p>
<p>Isso era um Page Controller que já é um pattern ultrapassado, isso é pré adoção do modelo MVC que tínhamos no Java no framework Struts, lançado só alguns meses antes, e que era muito melhor. Mas pra quem era de ASP puro, o Page Controller já era um patttern bacana.</p>
<p>A segunda coisa era a idéia de manter sessões em banco de dados. O ASP puro mantinha sessões de usuários em memória. O problema é se você queria escalar horizontalmente. Agora se o load balancer mandasse o usuário pra um servidor diferente, ele perdia a sessão. Então você era obrigado a configurar o load balancer pra usar uma coisa chamada session affinity que fazia o mesmo usuário sempre cair no mesmo servidor pra manter a sessão. Isso era bem ruim na verdade.</p>
<p>No ASP.Net você podia escolher StateServer e com isso manter a sessão no banco de dados, daí podia escalar horizontalmente mais fácil. Isso foi antes de inventarmos o termo “arquitetura shared-nothing”.</p>
<p>O que eu resolvi fazer? Um framework em ASP puro que fazia essas duas coisas. Dá uma olhada. (puxa)</p>
<p>Eu basicamente criei classes pra cada elemento de um form pra representar no lado do servidor. Coloquei coisas que o ASP não tinha nativo como MD5 e Base64.</p>
<p>Eu tinha uma classe que representava uma Page (já que o pattern é o Page Controller onde cada página HTML tinha um controller no back end).</p>
<p>Eu fiz uma classe de Session que eu chamei de SessionPlus pra substituir o Session in memory do ASP por um que gravava em banco.</p>
<p>Eu fiz uma classe pra encapsular até o gerenciamento de upload de arquivos. Antes a gente lidava direto com o blob que vinha num form multipart.</p>
<p>Eu já tinha até o conceito de Validators, eu tinha classes ValidatorRequired que é como o validates presence de um ActiveRecord do Rails e até um Validator Regex com patterns pré-configurados pra regex comuns.</p>
<p>Cada classe cuidava da renderização HTML de cada elemento segundo a parametrização e aí no HTML em si eu precisava colocar o mínimo possível pra posicionar os elementos.</p>
<p>Eu até quis fazer um sistema de challenge-response. Em vez de postar a senha do usuário no form, eu carregava um gerador de MD5 no lado do Javascript. <br>
Daí o framework quando renderizava o HTML do Form também colocava um challenge num campo hidden do form, meio como hoje a gente coloca um meta tag pra evitar cross site request forgery.
Daí antes de submeter o form eu fazia um md5 do usuário, senha e esse challenge, daí no servidor era só fazer o mesmo md5 e comparar, daí eu não trafegava a senha. Ah sim, era md5 porque na época ainda não sabíamos que md5 tinha defeitos, só depois passamos a usar sha1.</p>
<p>Não era um esquema muito sofisticado nem muito seguro na verdade, mas em 2001 meu conhecimento de segurança só ia até aí. Eu tinha programado jeitos de evitar um pouco o cross site, e também duplo post ou post back.</p>
<p>Tudo foi escrito em VBScript! … é eu sei &hellip; que era uma versão menor do Visual Basic 6. Mas eu fui um pouco além. Em vez de deixar esse código junto com o código da aplicação, eu aprendi que era possível encapsular VBScript num XML em formato WSC que era Windows Script Components. Ele funciona como um componente COM binário e eu podia instalar no servidor de aplicação COM+ Components que vinha carregado em todo Windows Server e expor método públicos como CreateInstance e aí eu podia instanciar os objetos no servidor COM+ e usar de uma página ASP.</p>
<p>Normalmente você precisaria usar C++ ou Visual Basic 6 pra fazer componentes COM. Não sei porque, parecia que quase ninguém usava VBScript pra fazer componentes. A falta que fazia um site como stackoverflow …. Pensa que não existia o conceito de um NPM ou Nugget o máximo que tínhamos em open source era CPAN pra Perl, mas em Windows, nada. Ah, e não existia Git e nem Subversion, e vou te dizer que CVS era um parto pra usar.</p>
<p>Eu vou dizer que, pra ser 17 anos atrás, até que não tá tão ruim. Em em PHP ou Perl ainda não tinha muito o conceito de um “web framework” como temos hoje. No Java mesmo tava começando isso.</p>
<p>Agora, a parte ruim? Eu gastei um tempão fazendo os sites do jeito “normal” macarrônico em ASP e em paralelo eu ficava tentando fazer esse framework. Mas quando eu terminei ele, a bolha da internet tava crasheando e no final eu acabei não usando esse framework em nada muito útil.</p>
<p>Eu estava usando VBScript e ASP pra fazer coisas que eles não foram feitos pra fazer. A linguagem em si era bastante limitante. Mas o ponto foi pra dizer que eu fiz tudo que era possível fazer e também tentei dobrar a linguagem além do que deveria.</p>
<p>Foram acho que uns 3 anos programando todo tipo de intranet, extranet, website e tudo mais com ASP. Isso foi no ápice já do Late Majority do mundo ASP na curva de adoção.</p>
<p>ASP Clássico ainda iria durar muito tempo, eu reencontraria projetos em ASP em 2003, 2004 até 2008 e 2009. Mas esse framework pra mim serviu pra demonstrar as limitações do ASP e eu sabia que não havia mais muito o que fazer. O que poderia ser feito já havia sido feito. Era hora de abandonar o navio e foi o que eu fui gradativamente fazendo ao longo de 2002 e 2003.</p>
<p>Mesmo ainda haveria mercado pelos próximos 10 anos, mas já eram os Late Majority começando a virar Laggard, pelo simples fato que a Microsoft já deu sinais de abandonar o ASP pra perseguir o ASP.NET e como ASP não era open source, era o fim da linha.. O crash da internet meio que me forçou a mudar de rumo de qualquer jeito e depois disso eu caí no mundo SAP e Java de cabeça, que é tema pra outro episódio.</p>
<p>Porque eu contei essa história? Porque não importa qual linguagem ou plataforma eu pegasse, eu tentava sempre encontrar os limites. Não só seguir os tutoriais, ou os padrões, mas quebrar os padrões. É o que todo programador open source meio que tenta fazer hoje em dia também. O maior problema é que quando você investe 3 ou 4 anos da sua vida estudando e treinando com afinco, quando você conhece cada detalhe da tecnologia, a zona de conforto é muito sedutor. Você já é o senior, o melhor da tecnologia, poucos sabem tanto quanto você sabe.</p>
<p>Eu tive a sorte de recomeçar como junior em outra plataforma e fazer tudo de novo. Os primeiros 15 anos da minha carreira acho que de 1991 até 2006 foram consecutivos reinícios. Eu fui tecnicamente junior a maior parte da minha carreira, porque eu ficava reiniciando em novas plataformas o tempo todo, só depois de 2008 eu comecei a me aceitar como “senior”. E não foi de propósito, eu disse que a bolha da internet foi um momento interessante. O pico e o crash aconteceram de uma só vez, mas os anos que se antecederam pra chegar na bolha já eram acelerado. Não é muito diferente de como é hoje.</p>
<p>Eu não conhecia o conceito ainda de curva de adoção, mas intuitivamente eu tinha a noção. Mas não foi perda de tempo. Todos os conceitos que eu pratiquei em detalhes fazendo o framework deste episódio serviram pra me ajudar a avaliar melhor os frameworks que vieram depois. O ASP clásico e VBScript morreram, mas os conceitos de gerenciamento de sessão, validadores de form, gerenciadores de upload, separação de lógica server-side e client-side, componentização, serialização de objetos, encoders e o básico de criptografia, continuam sendo conhecimentos válidos. E esses conceitos é só o que tem nesse mini-framework, mas no próximo episódio você vai ver uma coisa um pouco maior que eu fiz alguns meses antes desta história, vocês vão gostar.</p>
<p>Meu código de 2001, comparado com os padrões de hoje em dia, é bem ruim. Bem ruim mesmo. Mas se alguém tiver curiosidade, eu subi esse mini-framework no meu Github, coloquei o link na descrição do vídeo. Não deixem de comentar abaixo, conta pra gente você também já fez seu próprio framework? Se curtiram mandem um joinha, assinem o canal e clique no sininho.</p>
]]></content:encoded><category>desenvolvimento-web</category><category>arquitetura-de-software</category><category>programacao</category><category>akitando</category></item><item><title>Por que Falar Mal do Ruby on Rails é Simplesmente Preguiça</title><link>https://akitaonrails.github.io/2017/08/03/por-que-falar-mal-do-ruby-on-rails-e-simplesmente-preguica/</link><guid isPermaLink="true">https://akitaonrails.github.io/2017/08/03/por-que-falar-mal-do-ruby-on-rails-e-simplesmente-preguica/</guid><pubDate>Thu, 03 Aug 2017 18:01:00 -0300</pubDate><description>&lt;p&gt;É inevitável hoje em dia: de tempos em tempos vai aparecer um artigo proclamando a morte do Ruby on Rails. É o click-bait mais fácil do mundo, como &lt;a href="https://thenextweb.com/dd/2017/07/26/ruby-rails-major-coding-bootcamp-ditches-due-waning-interest/#.tnw_kWT917pu"target="_blank" rel="noopener"&gt;esse aqui do TNW&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Você pode responder &lt;em&gt;&amp;ldquo;mais um fanboy de Ruby.&amp;rdquo;&lt;/em&gt; Tudo bem, é um argumento justo de fazer, mas terrível de sustentar, porque é o clássico e raso &lt;a href="https://en.wikipedia.org/wiki/Ad_hominem"target="_blank" rel="noopener"&gt;argumentum ad hominem&lt;/a&gt;. E já que estamos no assunto de falácias, o artigo click-bait acima está errado exatamente porque cai numa &lt;a href="https://en.wikipedia.org/wiki/Post_hoc_ergo_propter_hoc"target="_blank" rel="noopener"&gt;Post hoc ergo propter hoc&lt;/a&gt; descarada, temperada com um bom tanto de &lt;a href="https://en.wikipedia.org/wiki/Confirmation_bias"target="_blank" rel="noopener"&gt;viés de confirmação&lt;/a&gt; — do qual todos nós somos culpados o tempo todo.&lt;/p&gt;</description><content:encoded><![CDATA[<p>É inevitável hoje em dia: de tempos em tempos vai aparecer um artigo proclamando a morte do Ruby on Rails. É o click-bait mais fácil do mundo, como <a href="https://thenextweb.com/dd/2017/07/26/ruby-rails-major-coding-bootcamp-ditches-due-waning-interest/#.tnw_kWT917pu"target="_blank" rel="noopener">esse aqui do TNW</a>.</p>
<p>Você pode responder <em>&ldquo;mais um fanboy de Ruby.&rdquo;</em> Tudo bem, é um argumento justo de fazer, mas terrível de sustentar, porque é o clássico e raso <a href="https://en.wikipedia.org/wiki/Ad_hominem"target="_blank" rel="noopener">argumentum ad hominem</a>. E já que estamos no assunto de falácias, o artigo click-bait acima está errado exatamente porque cai numa <a href="https://en.wikipedia.org/wiki/Post_hoc_ergo_propter_hoc"target="_blank" rel="noopener">Post hoc ergo propter hoc</a> descarada, temperada com um bom tanto de <a href="https://en.wikipedia.org/wiki/Confirmation_bias"target="_blank" rel="noopener">viés de confirmação</a> — do qual todos nós somos culpados o tempo todo.</p>
<p>Não estou dizendo que o autor escreveu essas falácias de propósito. Infelizmente, é fácil demais cair nelas. Especialmente quando todo mundo tem um desejo intrínseco de confirmar suas próprias crenças. Mesmo tentando ser cuidadoso, eu mesmo acabo fazendo isso.</p>
<p>Sobre o assunto específico de Rails, comece por aqui:</p>
<ul>
<li><a href="http://www.akitaonrails.com/2016/05/23/rails-venceu-o-elefante-na-sala"target="_blank" rel="noopener">Rails has won: The Elephant in the Room</a></li>
<li><a href="http://www.akitaonrails.com/2016/08/19/a-comunidade-ruby-e-a-reputacao"target="_blank" rel="noopener">The Ruby Community and Reputation</a></li>
<li><a href="http://www.akitaonrails.com/2017/05/14/guilda-de-programadores-religiao-e-esportes"target="_blank" rel="noopener">Programmers Guild - Religion and Sports</a></li>
</ul>
<p>Como regra geral, qualquer pessoa anunciando a morte futura de qualquer coisa está provavelmente errada. É divertido fazer apostas, mas artigos em sites de alto perfil como o TNW deveriam ter mais cuidado, já que muitos iniciantes acabam acreditando piamente nessas opiniões.</p>
<p>É por isso que eu sempre digo às pessoas: parem de confiar em opiniões. Todo mundo tem uma opinião, e é exatamente por isso que elas são tão baratas. Aí as pessoas artificialmente elevam &ldquo;celebridades&rdquo; ou &ldquo;gurus&rdquo; ao status de quem tem opiniões de maior valor. Eu não compro isso, e você também não deveria.</p>
<p><a href="https://en.wikipedia.org/wiki/Black_swan_theory"target="_blank" rel="noopener">Cisnes Negros</a> acontecem. Mas tentar prevê-los é exercício de futilidade. Você pode ter sorte uma ou duas vezes. Mas tentativas sistemáticas são inúteis. Por isso sou um grande defensor da <a href="https://en.wikipedia.org/wiki/Antifragility"target="_blank" rel="noopener">Antifrabilidade</a> — e vou te dar as regras gerais para entender o que fazer.</p>
<p>Estou dizendo que Ruby on Rails vai durar para sempre? Não seja ridículo, claro que não. Nada dura para sempre. Mais de 2 décadas trabalhando ativamente na área me ensinaram que apostar tudo num único cavalo é um erro. Vale no mercado financeiro, vale no mercado de tecnologia.</p>
<p>Em vez de ações ou títulos, o seu ativo é o seu conhecimento e a sua prática. Diversificação de portfólio costuma ser a aposta mais inteligente.</p>
<p>Você colocou todo o seu dinheiro em Bitcoins. Agora vai fazer de tudo para confirmar esse viés, assinando todo noticiário e opinião que confirma sua aposta, e vai atacar qualquer artigo que apresente um argumento contrário. Estou dizendo para não investir em Bitcoins? De forma alguma. Só não seja burro a ponto de colocar tudo num único ativo.</p>
<p>Você gastou todo o seu tempo aprendendo <a href="https://en.wikipedia.org/wiki/Google_Web_Toolkit"target="_blank" rel="noopener">Google Web Toolkit</a> em 2006. Boa sorte encontrando artigo que confirme esse viés agora. Esse é um daqueles casos post-factum em que só nos resta concordar consensualmente sobre a morte da coisa.</p>
<blockquote>
  <p>O trabalho de um programador profissional de verdade não é dominar uma única ferramenta ou mesmo um único stack tecnológico. Seu trabalho é ser o melhor possível em aprender e praticar na área de tecnologia.</p>

</blockquote>
<p>Dito isso, e mesmo que a estrutura do artigo seja imprecisa, o sentimento não está de todo errado. Ruby on Rails não representa mais o que representava há 10 anos. Mas antes de entrar em pânico, vamos explorar essa história juntos.</p>
<h3>2003 - Um Novo Renascimento<span class="hx:absolute hx:-mt-20" id="2003---um-novo-renascimento"></span>
    <a href="#2003---um-novo-renascimento" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Num exercício retrospectivo, é preciso reconhecer que uma série de eventos ajudou a desdobrar o fenômeno Rails entre 2003 e 2011. Não foi por design. Não foi planejado. Poderia ter sido outra coisa qualquer.</p>
<ul>
<li>Estávamos nos recuperando do crash da bolha dot-com de 2001.</li>
<li>Redes sociais começavam a emergir: Orkut, MySpace, Friendster. Facebook ainda nem estava no radar.</li>
<li>Blogs ganhavam força e competiam com os veículos de notícia tradicionais.</li>
<li>Os grandes fornecedores focavam exclusivamente no mercado Enterprise e o resto era considerado amadorismo.</li>
<li>No mainstream, ou você fazia .NET ou Java no Enterprise, ou fazia hotsites em Flash e PHP para agências.</li>
<li>Até ferramentas open source como o Eclipse eram usadas principalmente dentro de umbrellas enterpriseiras como WebSphere ou WebLogic.</li>
<li>O Manifesto Ágil tinha acabado de ser publicado em 2001 e o mercado mainstream não ligou muito. PMI e RUP eram a ordem do dia.</li>
</ul>
<p>Então chegamos a 2004.</p>
<ul>
<li>O Google lançou o Gmail com grande repercussão. HTML dinâmico e Ajax demonstraram como era possível criar aplicações de nível Desktop na Web. É aí também que o Javascript ganha um novo papel, além de mini jogos burros ou anúncios.</li>
<li>O Facebook explodiu e tivemos os primeiros vislumbres do que redes sociais granulares pareciam.</li>
<li>A Apple consolidou seu retorno como gigante tech relevante com a ressurreição bem-sucedida do OS X, provando que Unix no desktop do consumidor era viável.</li>
<li>A Microsoft entrou no século XXI depois de um pesadíssimo processo antitruste que quase a destruiu. Bill Gates saiu, Steve Ballmer assumiu as rédeas e eles viveram sua pior década.</li>
<li>Anos de lock-in de fornecedor finalmente começaram a cansar os desenvolvedores; ninguém de verdade queria mais usar IBM, BEA, Oracle — e a promessa da revolução open source iniciada nos anos 90 finalmente começava a se solidificar.</li>
<li>O crash dot-com deixou uma depressão no mercado tech, mas também deixou para trás uma infraestrutura que finalmente estava sendo bem aproveitada.</li>
</ul>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/654/s-l225.jpg" alt="How Bill Blew it"  loading="lazy" /></p>
<p>O Ruby on Rails aparece como uma pequena curiosidade, numa conferência tech obscura no Brasil, criado por uma agência pequena mas promissora chamada 37signals. Sinalizando algo que a maioria dos desenvolvedores nunca tinha visto antes: uma linguagem flexível — diferente de Java, C# ou PHP — que podia ser usada para implementar aplicações web estilo Gmail com a estética da Apple, se afastando de tudo que Microsoft e IBM representavam, e incorporando técnicas ágeis como Test-Driven Development de forma prática e já inclusa.</p>
<p><a href="https://www.youtube.com/watch?v=Gzj723LkRJY"target="_blank" rel="noopener"><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/655/75879992_fa890a9a07.jpg" alt="15 minute blog"  loading="lazy" /></a></p>
<p>O que mais você poderia pedir em 2005?</p>
<p>Então chega <strong>2006</strong>: a Amazon AWS lança S3 e EC2 e o termo &ldquo;Elastic Cloud&rdquo; vira o novo hype.</p>
<p>Até aquele ponto, &ldquo;desenvolvimento web&rdquo; significava ou departamentos de TI caros e burocráticos em grandes corporações, ou hospedagem compartilhada barata mas fraca em serviços como Dreamhost, 1&amp;1, Media Temple.</p>
<p>A Elastic Cloud tinha a chave para superar o Enterprise e tornar possível que desenvolvedores independentes construíssem a tão sonhada &ldquo;escala web&rdquo;.</p>
<p>O Ruby on Rails estava exatamente onde precisava estar: começando a ser adotado por uma nova safra de startups do Vale do Silício tentando fazer a diferença nessa nova era Ajax + Cloud. Lembre que o Rails foi lançado com RJS, uma forma de escrever Javascript via Ruby usando Prototype e Scriptaculous. O jQuery nem era mainstream ainda.</p>
<p>O Twitter aparece. O Groupon aparece. A Engine Yard aparece. E muito mais vem na sequência.</p>
<p>Tudo isso acontecendo numa velocidade absurda — e estou apenas arranhando a superfície aqui.</p>


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

<p><strong>2007</strong> chega e pelo menos 3 coisas grandes acontecem:</p>
<ul>
<li>Nasce outra startup baseada em Rails: Heroku. Se o EC2 da AWS cunhou o termo &ldquo;Infrastructure as a Service&rdquo; (IaaS), o Heroku definiu o que &ldquo;Platform as a Service&rdquo; (PaaS) significa. Conseguiu aproveitar o EC2 e tornar seu poder acessível.</li>
<li>Ao mesmo tempo, desenvolvedores tinham ficado frustrados com sistemas de controle de versão como CVS, e as opções dos grandes fornecedores como Rational ClearCase, Microsoft SourceSafe eram simplesmente terríveis. O Subversion tinha surgido, mas suas muitas limitações seriam sua perdição. Linus Torvalds decide resolver o problema e lança o Git. Desenvolvedores mainstream finalmente aprendem termos básicos de ciência da computação como DAGs (&ldquo;Directed Acyclic Graphs&rdquo;).</li>
<li>O iPhone é lançado e uma nova revolução mobile começa. Players dominantes como Nokia e Blackberry foram abalados até o núcleo. Desenvolvedores queriam uma fatia disso. O Mobile Safari vira destaque da noite para o dia. E o WebKit sobe ao estrelato, eclipsando veteranos como Firefox/Gecko e Opera.</li>
</ul>
<p><strong>2008</strong>: o nascimento do GitHub e o alvorecer do DevOps.</p>
<p>O Git era ótimo, mas baixo nível demais. Vários outros concorrentes tentaram se tornar o novo padrão — Mercurial, Bazaar. Mas o nascimento do GitHub mudou tudo isso. E era mais uma startup baseada em Rails nas manchetes.</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/653/Theres-An-App-for-That-Tanglewood-Moms.jpg" alt="There’s an App for that"  loading="lazy" /></p>
<p>Mas a lua de mel mostrava sinais de esgotamento: o proeminente desenvolvedor Ruby Zed Shaw <a href="https://techcrunch.com/2008/01/01/zed-shaw-puts-the-smack-down-on-the-rails-community/"target="_blank" rel="noopener">saiu da comunidade em alto e bom som</a> em janeiro.</p>
<p>Nesse mesmo ano tivemos a guerra fria entre o framework Merb da Engine Yard e o Rails da 37signals, que resultou no <a href="http://yehudakatz.com/2008/12/23/rails-and-merb-merge/"target="_blank" rel="noopener">controverso merge Rails-Merb</a>, abrindo mais uma rachadura na comunidade.</p>
<p>Mas ainda tínhamos muito trabalho pela frente, então seguimos em frente.</p>
<p><strong>2009</strong>: &ldquo;tem um app pra isso!&rdquo;</p>
<p>A Apple finalmente lança um SDK nativo de verdade. Desenvolvedores começam a construir uma nova geração de apps de produtividade e jogos. O WebKit cresce em participação de mercado. A Apple solidifica o conceito de estética no desenvolvimento de software e o Ruby on Rails está no seu auge nesse ponto.</p>
<p>Começa a era NoSQL. Quando armazenamento e busca de dados realmente viram um gargalo, rapidamente aprendemos que bancos de dados enterpriseiros tradicionais não são &ldquo;web scale&rdquo; o suficiente. O MongoDB lidera o bando. O desenvolvimento web entra numa era de frenesi com NoSQL.</p>
<p>Em paralelo, o Twitter, que foi o cartão-postal da geração Rails, abre uma rachadura na comunidade Ruby ao declarar que começou a usar mais uma linguagem obscura: <a href="http://www.artima.com/scalazine/articles/twitter_on_scala.html"target="_blank" rel="noopener">Scala</a>!</p>
<p>Isso dá início à era da obsessão sem fim com &ldquo;concorrência&rdquo; — mais um conceito escorregadio sobre o qual muita gente fala bonito mas poucos de fato entendem o que significa. É o início da janela de oportunidade para Node.js, Scala, Erlang, Clojure, Go.</p>
<p>A lua de mel acabou.</p>
<p><strong>2010</strong>:</p>
<ul>
<li>A morte da Sun e a Oracle assumindo o Java. Se as coisas já não estavam boas para o Java, isso foi um prego pesado no caixão.</li>
<li>A Apple declara guerra aberta ao Flash. Mais um grande empurrão para Javascript e apps web front-end pesados.</li>
</ul>
<p><a href="https://www.apple.com/hotnews/thoughts-on-flash/"target="_blank" rel="noopener"><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/656/apple-no-flash-250x376.jpg" alt="Thoughts on Flash"  loading="lazy" /></a></p>
<p>Na minha opinião pessoal, esse é o ano em que o Rails atingiu seu pico de influência. Era o stack obrigatório para desenvolvedores em Macs que queriam criar sua nova Lean Startup do zero.</p>


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

<h3>2011: Uma Nova Era<span class="hx:absolute hx:-mt-20" id="2011-uma-nova-era"></span>
    <a href="#2011-uma-nova-era" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Em cerca de 6 anos, o mercado de desenvolvimento de software fez mudanças titânicas:</p>
<ul>
<li>O desenvolvimento distribuído derivado de anos de workflow Open Source se torna o novo padrão. Git e GitHub foram peças-chave. Os grandes fornecedores precisaram correr atrás com suas alternativas enterprise mal cheirosas como o TFS.</li>
<li>O Ágil passa a ser levado a sério por uma nova safra de startups tech que evangelizavam desenvolvimento de software de qualidade. Os testes deixam de ser coisas improdutivas e chatas para se tornarem técnica central e inestimável. Ferramentas como Hudson/Jenkins combinadas com o workflow do Git pavimentam o caminho para o que a integração contínua e depois o deploy contínuo se tornariam.</li>
<li>A AWS e o exemplo do Heroku pavimentam o caminho para tornar a TI antiga obsoleta e criar uma nova geração de infraestrutura como software, o &ldquo;DevOps&rdquo;. Surge a urgência de fazer deploy em escala web com zero atrito. O time-to-market vira fator-chave: release cedo, release com frequência.</li>
<li>A Apple lança a AppStore e ajuda a impulsionar o mercado de Software como Serviço, com apps precisando de internet ao vivo para funcionar. A &ldquo;Cloud&rdquo; chega às massas.</li>
<li>Ao fazer com que todo ser humano no planeta carregasse um dispositivo poderoso e sempre conectado — um smartphone —, nunca estivemos tão perto do futuro orwelliano de 1984. Tudo que uma pessoa faz vira ponto de dados. Big Data se torna uma coisa. Ciência de Dados se torna uma profissão viável no mainstream. Disciplinas relacionadas à IA se tornam necessidade para raciocinar sobre tanto dado.</li>
</ul>
<p>O Android se aproxima do iPhone. A Microsoft ressurge das cinzas. Em outubro, Steve Jobs morre, marcando o fim da Era de forte influência da Apple.</p>
<p>Mas por volta de 2011 outra coisa acontece: nós vencemos.</p>
<p>Desenvolvedores independentes superaram o status quo dos desenvolvedores enterprise. Houve uma virada de poder. As convenções para o campo do desenvolvimento de software não vêm mais dos velhos fornecedores de ferramentas. Não estamos mais ouvindo fabricantes de IDEs como Microsoft, BEA, Oracle. Estamos ouvindo os vencedores dessa geração de startups: Apple, Google, Facebook.</p>
<p>O conceito de <em>&ldquo;uma linguagem para governar todas&rdquo;</em> foi destroçado no campo web pelo Ruby on Rails e no espaço de aplicações nativas pela Apple. Duas linguagens muito incomuns e estranhas, Ruby e Objective-C, atingiram o mainstream. Elas provaram que desenvolvedores podem aprender e dominar linguagens exóticas transformando-as em produtos lucrativos.</p>
<p>Com essas bases estabelecidas, uma nova <strong>corrida do ouro</strong> emerge: linguagens novas para todo lado, livres da noção de que para ter sucesso era preciso ficar em Java, C#, C++ — as pessoas finalmente começaram a olhar para outros lados para se diferenciar, e assim uma nova geração de linguagens exóticas chega: Scala, Haskell, Ocaml, F#, Erlang, Clojure, Rust, Elixir, etc. O céu é o limite.</p>
<p>Ao matar o Flash e acelerar HTML 5, ES6, e com toda a controvérsia em torno de concorrência, o Javascript ganhou nova vida e uma adoção rápida como nada antes.</p>
<h3>Epílogo<span class="hx:absolute hx:-mt-20" id="epílogo"></span>
    <a href="#ep%c3%adlogo" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O Ruby on Rails está morto? Não.</p>
<p>O Ruby on Rails ainda é o líder do mercado? Não.</p>
<p>Qual é o legado da Geração Ruby on Rails? Ele teve um papel importante em definir o que hoje consideramos o novo status quo do desenvolvimento web: Git, CI, CD, Cloud, Métricas. Trouxe à mesa, com força, o pensamento estético da Apple.</p>
<p>Essa nova safra de startups tech teve um efeito colateral sério, porém: colocou novos engenheiros nos holofotes, elevando-os ao nível de rock stars e transformando-os em pretendentes a Steve Jobs. Eles simplesmente começaram a tomar tudo que conquistamos como garantido. Receita para o desastre.</p>
<p>E embora a influência dos grandes fornecedores tenha ficado rala comparada aos anos 90, eles não estão mortos. Depois de 2011 entramos numa nova era de caos, uma era de desenvolvedores insatisfeitos. Onde a cada dois anos surge uma nova derivação e temos aquela sensação insatisfatória de precisar reescrever tudo o tempo todo.</p>
<p>O pool do Ruby on Rails se diluiu em linguagens de nicho menores como o próprio Javascript, Go, Elixir, Scala.</p>
<p>O mainstream Java/Microsoft está ganhando nova tração. A estética também se diluiu. A engenharia cuidadosa está se diluindo. A ubiquidade das redes sociais e o caminho fácil do deploy rápido na nuvem estão tornando as pessoas cada vez menos pacientes. Artesanato requer paciência. As pessoas anseiam por Likes, uma nova geração de métricas de vaidade surgiu.</p>
<p>Temos agora um número maior de desenvolvedores independentes não-enterprise com pouca ou nenhuma paciência, com um senso exagerado de confiança, síndrome de <a href="https://en.wikipedia.org/wiki/Not_invented_here"target="_blank" rel="noopener">not-invented-here</a>, tomando tudo como garantido e se importando cada vez menos com valor de verdade.</p>
<p>Concluindo: a morte do Ruby on Rails está sim muito exagerada, ele vai continuar por muitos anos ainda — especialmente se o Ruby Core Team conseguir entregar de fato a <a href="https://blog.heroku.com/ruby-3-by-3"target="_blank" rel="noopener">meta 3x3</a>. As pessoas adoram compartilhar artigos de doom enquanto relatórios otimistas e mais realistas como <a href="https://reinteractive.com/posts/320-why-ruby-on-rails-is-still-the-best-choice"target="_blank" rel="noopener">Por que Ruby on Rails ainda é a melhor escolha?</a> são amplamente ignorados. É assim que funciona a vaidade das redes sociais.</p>
<p>Mas não vai ser o que foi, porque os tempos mudaram. Hoje podemos viver pelo workflow &ldquo;On Rails&rdquo; sem o Ruby on Rails. E ele não pode continuar quebrando o status quo, porque ele se tornou o status quo.</p>
<p>Fico curioso sobre para onde isso vai nos levar. Tenho algumas pistas, mas como disse, não estou no negócio de publicar manchetes click-bait.</p>
]]></content:encoded><category>rails</category><category>desenvolvimento-web</category><category>engenharia-de-software</category></item><item><title>Atualizando Meus Posts Antigos Sobre Uploads</title><link>https://akitaonrails.github.io/2016/07/28/atualizando-meus-posts-antigos-sobre-uploads/</link><guid isPermaLink="true">https://akitaonrails.github.io/2016/07/28/atualizando-meus-posts-antigos-sobre-uploads/</guid><pubDate>Thu, 28 Jul 2016 11:56:00 -0300</pubDate><description>&lt;p&gt;Felizmente as coisas costumam mudar para melhor, e práticas que &amp;ldquo;funcionavam ok&amp;rdquo; no passado podem estar obsoletas hoje.&lt;/p&gt;
&lt;p&gt;Isso acontece com posts antigos de blog, incluindo os meus, já que venho postando há vários anos.&lt;/p&gt;
&lt;p&gt;Neste post vou corrigir o que escrevi antes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.akitaonrails.com/2014/12/18/small-bites-direct-upload-para-s3-a-solucao-definitiva"target="_blank" rel="noopener"&gt;Direct Upload para S3: a Solução Definitiva!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.akitaonrails.com/2014/03/26/heroku-tips-s3-direct-upload-carrierwave-sidekiq"target="_blank" rel="noopener"&gt;S3 Direct Upload + Carrierwave + Sidekiq&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.akitaonrails.com/2010/06/23/akita-responde-upload-de-arquivos"target="_blank" rel="noopener"&gt;Upload de Arquivos&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.akitaonrails.com/2012/04/20/heroku-tips-enciclopedia-do-heroku"target="_blank" rel="noopener"&gt;Enciclopédia do Heroku&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Eles ainda são bons posts para entender a mecânica do que vou propor aqui, então vale a pena ler se você tem interesse em como as coisas funcionam por baixo.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Felizmente as coisas costumam mudar para melhor, e práticas que &ldquo;funcionavam ok&rdquo; no passado podem estar obsoletas hoje.</p>
<p>Isso acontece com posts antigos de blog, incluindo os meus, já que venho postando há vários anos.</p>
<p>Neste post vou corrigir o que escrevi antes:</p>
<ul>
<li><a href="http://www.akitaonrails.com/2014/12/18/small-bites-direct-upload-para-s3-a-solucao-definitiva"target="_blank" rel="noopener">Direct Upload para S3: a Solução Definitiva!</a></li>
<li><a href="http://www.akitaonrails.com/2014/03/26/heroku-tips-s3-direct-upload-carrierwave-sidekiq"target="_blank" rel="noopener">S3 Direct Upload + Carrierwave + Sidekiq</a></li>
<li><a href="http://www.akitaonrails.com/2010/06/23/akita-responde-upload-de-arquivos"target="_blank" rel="noopener">Upload de Arquivos</a></li>
<li><a href="http://www.akitaonrails.com/2012/04/20/heroku-tips-enciclopedia-do-heroku"target="_blank" rel="noopener">Enciclopédia do Heroku</a></li>
</ul>
<p>Eles ainda são bons posts para entender a mecânica do que vou propor aqui, então vale a pena ler se você tem interesse em como as coisas funcionam por baixo.</p>
<p><strong>TL;DR</strong>: use Cloudinary (com Attachinary) e instale um CDN (Cloudfront) agora mesmo, porque é trivial demais de fazer.</p>
<blockquote>
  <p>Upload de arquivos é uma tarefa que merece atenção séria. É um daqueles assuntos onde você não pode achar que &ldquo;funciona&rdquo; só porque funcionou na sua máquina de desenvolvimento, com meia dúzia de uploads triviais. O comportamento em produção muda drasticamente e pode derrubar seu projeto inteiro, dependendo do quanto ele depende de conteúdo gerado por usuário.</p>

</blockquote>
<p>São muitas peças móveis para entender. Mas nas soluções mais básicas você simplesmente cria um form HTML multipart com um campo de arquivo e submete direto para algum endpoint do controller. O controller recebe o blob binário e você salva em algum diretório local no servidor.</p>
<p>Essa é a <strong>pior solução possível</strong> e, infelizmente, é a que você mais encontra pela web.</p>
<p>Existem vários problemas para lidar com essa implementação ingênua:</p>
<ul>
<li>Estamos na era das câmeras de 12+ megapixels. Esses arquivos têm vários megabytes. Cada upload demora bastante, principalmente se o usuário está tentando subir o arquivo por uma rede sem fio lenta ou instável.</li>
</ul>
<p>Quando o navegador do usuário se conecta à aplicação web, ele segura aquela conexão e bloqueia o processo inteiro durante todo o ciclo de request-response. Para deixar simples: se você só tem 1 processo no servidor, ele fica incapaz de responder a qualquer outra requisição até o upload terminar.</p>
<p>Felizmente a maioria dos deploys usa NGINX na frente da aplicação web - como deveriam -, então esse efeito é minimizado, porque o NGINX recebe o blob inteiro antes de passar para o processo da aplicação por baixo, o que torna a coisa menos problemática na maioria dos casos.</p>
<p>Mas se você está no Heroku, a camada de router tem um limite de <strong>timeout de 30 segundos</strong>. Se o upload demorar mais que isso (o que é comum com usuários em redes ruins) essa camada vai cortar a conexão antes do upload terminar. Os usuários vão tentar de novo e isso pode fazer suas filas de requisição crescerem muito rápido.</p>
<p>Evite fazer upload de qualquer coisa diretamente para o Heroku. Eu considero isso uma <strong>feature</strong>, porque força aplicações deployadas no Heroku a usarem <strong>boas práticas</strong>, como vou explicar abaixo.</p>
<ul>
<li>Você também NÃO deve salvar arquivos no filesystem local se quer escalar horizontalmente, porque um servidor não vai conseguir enxergar o que está no outro, a menos que você esteja na AWS EC2 ou outro IaaS, com volumes montados compartilhados, por exemplo. No Heroku você não pode confiar em nada que esteja no filesystem, porque as máquinas são voláteis e sempre que um dyno reinicia ele perde tudo o que não estava lá no momento do deploy.</li>
</ul>
<p>Então você precisa subir o que recebeu para um storage externo, como o AWS S3, e isso também é lento se você recebe muitos uploads. Você pode contar com o NGINX ou com a camada de router do Heroku para receber os arquivos, mas o ciclo de request-response vai ficar lentíssimo se você processar e subir os arquivos para o S3 de forma síncrona.</p>
<p>A próxima opção que vem à cabeça é adicionar Sidekiq ou outro mecanismo de Async Job para fazer esse trabalho pesado. Agora você precisa criar placeholders no front-end para mostrar para os usuários enquanto o background job ainda não terminou de mandar os arquivos para o S3.</p>
<p>A última opção, se você cavar mais fundo, é o <strong>direct upload</strong> do navegador direto para o S3, postando só as URLs para sua aplicação web. Essa é a solução ideal, mas não é fácil de implementar na sua stack.</p>
<ul>
<li>Depois de acertar tudo isso, você ainda comete o erro de jogar as URLs do S3 direto nas tags IMG do seu HTML, o que não é recomendado pela própria Amazon, já que o S3 é recomendado apenas para fins de &ldquo;storage&rdquo;. Essa é a parte fácil de corrigir, mas a maioria dos projetos esquece dela.</li>
</ul>
<h3>Cloudinary ou Carrierwave Direct<span class="hx:absolute hx:-mt-20" id="cloudinary-ou-carrierwave-direct"></span>
    <a href="#cloudinary-ou-carrierwave-direct" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Nos meus posts anteriores eu expliquei cada um dos problemas acima em mais detalhe e ofereci algumas soluções (complicadas), como usar o projeto Refile. Naquela época os plugins do Carrierwave para Direct Upload ainda não estavam prontos.</p>
<p>Hoje em dia a solução é absurdamente fácil e é o que eu recomendo: vá direto para o <a href="http://cloudinary.com/documentation/rails_integration"target="_blank" rel="noopener">Cloudinary</a>. É uma solução Software as a Service para direct upload de verdade e processamento dinâmico de imagens. Vale muito explorar a gem <a href="https://github.com/assembler/attachinary"target="_blank" rel="noopener">Attachinary</a> para facilitar o processo.</p>
<p>Se você tem legado em Carrierwave, Paperclip, Shrine, Dragonfly, Refile ou qualquer outro, uma alternativa meio gambiarra - mas razoável - é simplesmente adicionar o Attachinary junto.</p>
<p>Digamos que você tem um uploader antigo <code>user.avatar</code>. Você pode simplesmente criar um novo <code>user.new_avatar</code> com Attachinary/Cloudinary. No upload do form HTML você passa a usar só os helpers do Attachinary. E no HTML onde você mostra a imagem em si, você pode criar um helper que checa por <code>@user.new_avatar?</code> e mostra usando <code>cl_image_tag(@user.avatar.path)</code>, caso contrário mostra as URLs antigas.</p>
<p>Você não precisa migrar tudo de uma vez para o Cloudinary. Basta deixar os assets antigos onde estão e começar a colocar os novos assets na configuração do Cloudinary. Ele tem um Free Tier que comporta até 75 mil imagens e permite tráfego de até 5GB. E com apenas USD 44 por mês você tem 10 vezes isso. Então é bem barato, se sua aplicação leva upload de arquivos a sério.</p>
<p>Se você ainda não quer se comprometer com um serviço externo, e está usando Carrierwave, outra opção que vale considerar é testar o <a href="https://github.com/dwilkie/carrierwave_direct"target="_blank" rel="noopener">CarrierwaveDirect</a> para experimentar direct upload do browser para o S3. Isso pelo menos vai te livrar das complexidades de configurar uploads assíncronos da aplicação web para o S3.</p>
<p>Update: o <a href="https://github.com/janko-m"target="_blank" rel="noopener">Janko Marohnić</a> postou um comentário muito bom que vale citar diretamente:</p>
<blockquote>
  <p>Great writeup, I wholeheartedly agree that we probably don&rsquo;t &ldquo;just want something simple&rdquo;, especially concerning synchronous uploads and CDNs.</p>

</blockquote>
<blockquote>
  <p>Shrine actually has a <a href="https://github.com/janko-m/shrine-cloudinary"target="_blank" rel="noopener">Cloudinary integration</a>, and it&rsquo;s pretty advanced too; it supports direct uploads, setting upload options, storing Cloudinary metadata (hello <a href="http://cloudinary.com/blog/introducing_intelligent_responsive_image_breakpoints_solutions"target="_blank" rel="noopener">responsive breakpoints</a>) etc :). Also, the Cloudinary gem ships with a CarrierWave integration. I think it&rsquo;s beneficial to use Shrine or CarrierWave with Cloudinary, because then you get to keep your file attachments library, and just change the underlying storage service.</p>

</blockquote>
<blockquote>
  <p>As for CarrierwaveDirect, I don&rsquo;t think it&rsquo;s actually very useful, because backgrounding you need to do all by yourself anyway, so basically all it gives you is the ability to generate a direct-upload form to S3. But generating request parameters and URL to S3 is something that&rsquo;s already built in into the official <a href="https://github.com/aws/aws-sdk-ruby"target="_blank" rel="noopener">aws-sdk</a> gem, CarrierwaveDirect just seems to be reimplementing that logic. And the advantage of generating this through aws-sdk is that it&rsquo;s not HTML-specific, so you could setup an endpoint which returns this information as JSON, and now you have your multiple file uploads directly to S3 :)</p>

</blockquote>
<p>Como disclaimer: eu mesmo não testei o CarrierwaveDirect e lembro que ele estava bem quebrado uns 2 anos atrás. E ouvi coisas boas sobre o Shrine ao longo do caminho, mas nunca fiz um teste decente. Recomendo experimentar se você quer migrar para longe do Carrierwave.</p>
<h3>Não sirva assets do S3, use um CDN, qualquer CDN<span class="hx:absolute hx:-mt-20" id="não-sirva-assets-do-s3-use-um-cdn-qualquer-cdn"></span>
    <a href="#n%c3%a3o-sirva-assets-do-s3-use-um-cdn-qualquer-cdn" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Se você vai continuar fazendo upload para o S3, pelo menos evite servir esses arquivos a partir do S3. No meu post da &ldquo;Enciclopédia do Heroku&rdquo; eu recomendava usar o &ldquo;S3 Assets Sync&rdquo;. NÃO FAÇA ISSO: use um CDN no lugar, é mais rápido, mais fácil e é a solução correta.</p>
<p>A primeira coisa a fazer é se cadastrar no serviço de CDN da AWS, o Cloudfront. Use sempre um CDN para todos os seus assets, não apenas os <a href="http://www.algonauti.com/posts/speed-up-ugc-with-cloudfront"target="_blank" rel="noopener">internos da sua aplicação web</a>, mas também todo conteúdo que o usuário sobe. É <a href="https://devcenter.heroku.com/articles/using-amazon-cloudfront-cdn"target="_blank" rel="noopener">super simples</a> criar um novo endpoint de CDN apontando para o seu bucket S3.</p>
<p>O Carrierwave por padrão usa o host do bucket S3, mas uma vez que você tem o endpoint do Cloudfront, dá para <a href="https://dzone.com/articles/carrierwave-heroku-cloudfront"target="_blank" rel="noopener">trocar facilmente</a> todas as URLs dos arquivos enviados 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/initializers/carrierwave.rb</span>
</span></span><span class="line"><span class="cl"><span class="no">CarrierWave</span><span class="o">.</span><span class="n">configure</span> <span class="k">do</span> <span class="o">|</span><span class="n">config</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="n">config</span><span class="o">.</span><span class="n">fog_credentials</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="ss">:provider</span> <span class="o">=&gt;</span> <span class="s2">&#34;AWS&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="ss">:aws_access_key_id</span> <span class="o">=&gt;</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;AWS_ACCESS_KEY_ID&#39;</span><span class="o">]</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="ss">:aws_secret_access_key</span> <span class="o">=&gt;</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;AWS_SECRET_ACCESS_KEY&#39;</span><span class="o">]</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="n">config</span><span class="o">.</span><span class="n">fog_directory</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;S3_BUCKET&#39;</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 apenas uma das 2 configurações abaixo</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># config.fog_host = &#34;http://#{ENV[&#39;S3_BUCKET&#39;]}.s3.amazonaws.com&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="n">config</span><span class="o">.</span><span class="n">fog_host</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;S3_CDN&#39;</span><span class="o">]</span> <span class="c1"># para cloudfront</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ê obviamente precisa configurar a variável de ambiente <code>S3_CDN</code> para apontar para o endpoint do Cloudfront específico do bucket S3 que está usando.</p>
<p>A parte importante é: sirva TODOS OS ASSETS de um CDN, não importa o quê. Nunca direto da sua aplicação web, evite servir direto do S3. É quase trivial de implementar e, se você não gosta da AWS por qualquer motivo, dá para escolher Fastly, Cloudflare e vários outros.</p>
<p>Todos funcionam da mesma forma. NÃO existe desculpa para não usar um CDN, não importa o tamanho da sua aplicação.</p>
]]></content:encoded><category>cloud</category><category>desenvolvimento-web</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><item><title>[Primeiros Passos] Elixir 1.0 + Phoenix Framework 0.13</title><link>https://akitaonrails.github.io/2015/06/02/primeiros-passos-elixir-1-0-phoenix-framework-0-13/</link><guid isPermaLink="true">https://akitaonrails.github.io/2015/06/02/primeiros-passos-elixir-1-0-phoenix-framework-0-13/</guid><pubDate>Tue, 02 Jun 2015 16:14:00 -0300</pubDate><description>&lt;p&gt;Ainda estou literalmente nos primeiros passos de aprender Elixir corretamente, mas achei interessante fazer um post demonstrando alguma coisa prática.&lt;/p&gt;
&lt;p&gt;Para quem ainda não sabia, existe uma linguagem chamada &amp;ldquo;Erlang&amp;rdquo; (para &amp;ldquo;Ericsson Language&amp;rdquo;), uma linguagem &amp;ldquo;quase-funcional&amp;rdquo; com nada menos que 29 anos de idade (lançada em 1986) cujo núcleo é uma virtual machine muito leve, com grande tolerância a falhas e alta concorrência com processos leves e primitivas simples.&lt;/p&gt;
&lt;p&gt;Elixir foi criado por ninguém menos que nosso conhecido Railer e Rubista José Valim para ser uma alternativa moderna de linguagem. A linguagem Erlang não é estranha da comunidade Ruby em geral pois Dave Thomas e Andy Hunt evangelizaram muito ela em 2007 pela Pragmatic Programmers. Mas a sintaxe realmente não é agradável para a maioria de nós. Para isso existe o Elixir: para que possamos usar toda a maturidade da VM do Erlang com uma sintaxe agradável com muitos traços de Ruby (embora não seja uma descendência direta).&lt;/p&gt;</description><content:encoded><![CDATA[<p>Ainda estou literalmente nos primeiros passos de aprender Elixir corretamente, mas achei interessante fazer um post demonstrando alguma coisa prática.</p>
<p>Para quem ainda não sabia, existe uma linguagem chamada &ldquo;Erlang&rdquo; (para &ldquo;Ericsson Language&rdquo;), uma linguagem &ldquo;quase-funcional&rdquo; com nada menos que 29 anos de idade (lançada em 1986) cujo núcleo é uma virtual machine muito leve, com grande tolerância a falhas e alta concorrência com processos leves e primitivas simples.</p>
<p>Elixir foi criado por ninguém menos que nosso conhecido Railer e Rubista José Valim para ser uma alternativa moderna de linguagem. A linguagem Erlang não é estranha da comunidade Ruby em geral pois Dave Thomas e Andy Hunt evangelizaram muito ela em 2007 pela Pragmatic Programmers. Mas a sintaxe realmente não é agradável para a maioria de nós. Para isso existe o Elixir: para que possamos usar toda a maturidade da VM do Erlang com uma sintaxe agradável com muitos traços de Ruby (embora não seja uma descendência direta).</p>
<p>Depois de alguns anos em desenvolvimento, o Valim fechou a versão 1.0 oficial em Setembro de 2014, então agora é um bom momento para investir tempo em aprender.</p>
<p>Este post não tem como objetivo ser um artigo altamente detalhado, apenas primeira impressões. Para aprender mais vá diretamente à fonte:</p>
<ul>
<li><a href="http://elixir-lang.org/getting-started/introduction.html"target="_blank" rel="noopener">Getting Started</a> - tutorial oficial</li>
<li><a href="http://elixir-lang.org/learning.html"target="_blank" rel="noopener">Learning Resources</a> - livros já publicados</li>
</ul>
<p>Felizmente, <a href="http://elixir-lang.org/install.html"target="_blank" rel="noopener">instalar o Elixir</a> hoje é quase tão simples como fazer um simples <tt>brew install elixir</tt> ou <tt>sudo apt-get install elixir</tt>. Veja a documentação oficial para ver  como instalar em seu sistema operacional de preferência.</p>
<p>De cara a primeira coisa que você precisa conhecer é a ferramenta <a href="http://elixir-lang.org/docs/v1.0/mix/"target="_blank" rel="noopener">Mix</a>. Para rubistas ela é como se fosse uma mistura do nosso Rake com Bundler (já o equivalente Rubygems é o <a href="https://hex.pm"target="_blank" rel="noopener">Hex</a>). Ele é responsável por executar tarefas, dentre as quais gerenciar dependências (você vai encontrar um arquivo &ldquo;<tt>mix.lock</tt>&rdquo; que é semelhante ao nosso <tt>Gemfile.lock</tt>). Por exemplo, depois de instalar se fizer <tt>mix new phoenix_crud</tt> ele vai criar uma estrutura de diretórios e arquivos padrão com sub-diretórios &ldquo;config&rdquo;, &ldquo;lib&rdquo;, &ldquo;test&rdquo; (veja o padrão das coisas serem testadas, como na comunidade Ruby), um arquivo &ldquo;README.md&rdquo; (veja a preferência por markdown, que é uma tendência), e um arquivo &ldquo;<tt>mix.exs</tt>&rdquo; que funciona como uma mistura de um arquivo <tt>Gemfile</tt> e um arquivo Gemspec.</p>
<p>Mas para sermos mais práticos, vamos direto ao assunto: usar o <a href="http://www.phoenixframework.org"target="_blank" rel="noopener">web framework Phoenix</a>. Em resumo, o Phoenix parece um Ruby on Rails mais simples. &ldquo;Mais simples&rdquo; porque ainda não houve tempo de maturação para ter mais, então cuidado, usar o Phoenix se parece um pouco com usar o Edge Rails (que é o Rails em desenvolvimento atualmente).</p>
<p>Até o momento deste post, o mais &ldquo;adequado&rdquo; talvez seja usar diretamente o que está no master do projeto, clonando e rodando diretamente do seu diretório:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>git clone git@github.com:phoenixframework/phoenix.git
cd phoenix/installer
mix phoenix.new /diretorio_de_projetos/phoenix_crud
cd /diretorio_de_projetos/phoenix_crud</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 o uso do comando <tt>mix</tt> com a tarefa <tt>phoenix.new</tt> que é como se existisse no Ruby algo como <tt>rake rails.new</tt> em vez do que fazemos hoje que é <tt>rails new</tt>. No mundo Rails o comando <tt>rails</tt> se sobressaiu ao uso do <tt>rake</tt> mas no mundo Elixir a idéia é manter consistente no comando <tt>mix</tt>.</p>
<p>O resto do artigo assume que você tem PostgreSQL instalado e entende minimamente como configurar roles, e que estamos já dentro do diretório do projeto Phoenix recém-criado.</p>
<p>Veja a estrutura inicial de um projeto Phoenix (&quot;~&quot; significa &ldquo;semelhante a&rdquo;):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>/_build            - provavelmente onde fica os binários compilados
/config            - ~ /config do Rails
  /config.exs      - ~ /config/application.rb
  /dev.exs         - ~ /config/environments/development.rb
  /prod.exs        - ~ /config/environments/production.rb
  /prod.secret.exs - ~ /config/secrets.yml
  /test.exs        - ~ /config/environments/test.rb
/deps              - meio como um RVM gemset e /vendor
/lib
  /phoenix_crud
    /endpoint.ex   - meio como um /config/application.rb
    /repo.ex       - onde configura repositório do Ecto (mais abaixo)
  phoenix_crud.ex  - meio como o /config.ru e /config/boot.rb
/node_modules      - ele vendoriza dependência do Node
/priv
  /repo
    /migration     - incrivelmente igual a /db/migrate
  /static          - quase o mesmo que /public/assets (depois de compilado)
    /css
    /images
    /js
/test              - muito próximo ao nosso /test ou /spec
  /channels
  /controllers
  /models
  /support
  /views
  test_helper.exs  - praticamente o /test/test_helper.rb
/web                        - quase o /app
  /channels                 - para coisas de Web Sockets (futuro equivalente a Action Cable no Rails 5)
  /controllers
    page_controller.ex      
  /models
  /static                   - quase o /app/assets
    /css
      app.scss
    /js
      app.js
    /vendor
      phoenix.js
  /templates
    /layout                 - ~ /app/views/layouts
      application.html.eex
    /page                   - ~ /app/views/pages
      index.html.eex
  /views                    - não é equivalente a /app/views
    error_view.ex           - &#34;acho&#34; que é o que abre acesso a contexto dentro dos templates
    layout_view.ex
    page_view.ex
  router.ex                 - ~ /config/routes.rb
  web.ex                    - cria acesso a contexto dentro de cada classe anterior
brunch-config.js            - Brunch em vez de Sprockets para Asset Pipeline
mix.exs                     - ~ Gemfile
mix.lock                    - ~ Gemfile.lock
package.json                - não existe equivalente ao nosso rails-assets.org
README.md</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 podem ver é incrivelmente próximo ao Rails e fica mais próximo ainda no código. Para começar, precisamos configurar o acesso ao PostgreSQL no arquivo <tt>config/dev.exs</tt> no trecho 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="c1"># Configure your database</span>
</span></span><span class="line"><span class="cl"><span class="n">config</span> <span class="ss">:phoenix_crud</span><span class="p">,</span> <span class="no">PhoenixCrud</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;postgres&#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;postgres&#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;phoenix_crud_dev&#34;</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">10</span> <span class="c1"># The amount of database connections in the pool</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-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ê já configurou um <tt>config/database.yml</tt>, é a mesma coisa. E já que somos meros iniciantes, nada como um bom e velho Scaffold! Como fazer?</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>mix phoenix.gen.html User users name:string email:string bio:string age:integer</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>Obs: neste instance, o projeto não é compilável. Falta alterar manualmente o arquivo <tt>/web/router.ex</tt>, que vamos fazer mais pra frente.</p>
<p>De qualquer forma, o comando anterior vai criar arquivos como:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<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/20150601195745_create_user.exs</span>
</span></span><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">PhoenixCrud</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">CreateUser</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">:users</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">:email</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">:age</span><span class="p">,</span> <span class="ss">:integer</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="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>Sem palavras! É praticamente a mesma DSL de migrations do Rails, o equivalente em Rails seria:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<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">CreateUser</span> <span class="o">&lt;</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Migration</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">change</span>
</span></span><span class="line"><span class="cl">    <span class="n">create_table</span> <span class="ss">:users</span> <span class="k">do</span> <span class="o">|</span><span class="n">t</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">      <span class="n">t</span><span class="o">.</span><span class="n">string</span> <span class="ss">:name</span>
</span></span><span class="line"><span class="cl">      <span class="n">t</span><span class="o">.</span><span class="n">string</span> <span class="ss">:email</span>
</span></span><span class="line"><span class="cl">      <span class="n">t</span><span class="o">.</span><span class="n">integer</span> <span class="ss">:age</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">t</span><span class="o">.</span><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="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>Um model, por outro lado, é um pouco diferente do ActiveRecord:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<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">PhoenixCrud</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">PhoenixCrud</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></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">: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">: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">:bio</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">:number_of_pets</span><span class="p">,</span> <span class="ss">:integer</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="n">email</span> <span class="n">bio</span> <span class="n">number_of_pets</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></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">  Creates a changeset based on the `model` and `params`.
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">  If `params` are nil, an invalid changeset is returned
</span></span></span><span class="line"><span class="cl"><span class="s2">  with no validation performed.
</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">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="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>Diretivas como o <tt>@required_fields</tt> funciona mais ou menos como declarar um <tt>validates :field, presence: true</tt>. A diferença maior é com o <tt>changeset/2</tt> (essa notação quer mais ou menos dizer: função com arity 2, ou seja, aceita 2 argumentos).</p>
<p>E de cara batemos com uma das funcionalidades que chamou mais atenção no Elixir, o operador &ldquo;pipe&rdquo; que é o <tt>|&gt;</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">model</span> <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></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-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 é a mesma coisa que:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<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">cast</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="vi">@required_fields</span><span class="p">,</span> <span class="vi">@optional_fields</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>É para os casos onde faríamos:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<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">foo</span><span class="p">(</span><span class="n">bar</span><span class="p">(</span><span class="n">baz</span><span class="p">),</span> <span class="n">options</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Se entendi direito, faríamos o seguinte com pipes:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>baz |&gt; bar() |&gt; foo(options)</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 voltando ao <tt>changeset</tt>, segundo a <a href="http://www.phoenixframework.org/v0.13.1/docs/ecto-models"target="_blank" rel="noopener">documentação no site do Phoenix</a> usaríamos desta forma:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<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">params</span> <span class="o">=</span> <span class="sx">%{name: &#34;Joe Example&#34;, email: &#34;joe@example.com&#34;, age: 15}</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="o">%</span><span class="no">User</span><span class="p">{},</span> <span class="n">params</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">changeset</span><span class="o">.</span><span class="n">valid?</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-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 Rails, seria mais ou menos o equivalente 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">params</span> <span class="o">=</span> <span class="p">{</span><span class="nb">name</span><span class="p">:</span> <span class="s2">&#34;Joe Example&#34;</span><span class="p">,</span> <span class="ss">email</span><span class="p">:</span> <span class="s2">&#34;joe@example.com&#34;</span><span class="p">,</span> <span class="ss">age</span><span class="p">:</span> <span class="mi">15</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="n">user</span> <span class="o">=</span> <span class="no">User</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">params</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">user</span><span class="o">.</span><span class="n">valid?</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-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 para realmente adicionar validações como no ActiveRecord, adicionamos quaisquer transformações ou validações ao pipeline do changeset, o que faz muito sentido:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<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">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="kp">nil</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">:age</span><span class="p">,</span> <span class="ss">min</span><span class="p">:</span> <span class="mi">18</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">:age</span><span class="p">,</span> <span class="ss">max</span><span class="p">:</span> <span class="mi">80</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="n">validate_format</span><span class="p">(</span><span class="ss">:email</span><span class="p">,</span> <span class="o">~</span><span class="n">r</span><span class="o">/</span><span class="err">@</span><span class="o">/</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>Com isso em mente, vejamos o próximo código que foi gerado automaticamente nesse scaffold, o controller:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<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/user_controller.ex</span>
</span></span><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">PhoenixCrud</span><span class="o">.</span><span class="n">UserController</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">PhoenixCrud</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">PhoenixCrud</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">plug</span> <span class="ss">:scrub_params</span><span class="p">,</span> <span class="s2">&#34;user&#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">:action</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">index</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">users</span> <span class="o">=</span> <span class="no">Repo</span><span class="o">.</span><span class="n">all</span><span class="p">(</span><span class="no">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;index.html&#34;</span><span class="p">,</span> <span class="ss">users</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><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">new</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">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="o">%</span><span class="no">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;new.html&#34;</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><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="sx">%{&#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">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="o">%</span><span class="no">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></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">changeset</span><span class="o">.</span><span class="n">valid?</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">insert</span><span class="p">(</span><span class="n">changeset</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</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 created 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">else</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;new.html&#34;</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><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">show</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">render</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="s2">&#34;show.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></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">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><span class="line"><span class="cl">
</span></span><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></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">changeset</span><span class="o">.</span><span class="n">valid?</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">update</span><span class="p">(</span><span class="n">changeset</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</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 updated 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">else</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><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">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></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><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>Novamente, de bater o olho é uma estrutura muito semelhante ao nosso conhecido controller restful do Rails. Actions com os mesmos nomes e mesmo o código é muito semelhante. Se tentar ler provavelmente vai entender rapidamente as diferenças em sintaxe.</p>
<p>Dentre as diferenças, imagino que <tt>alias PhoenixCrud.User</tt> é para que possamos usar diretamente dentro dos métodos assim: <tt>user = Repo.get(User, id)</tt> que, se você já entendeu, percebeu que é o equivalente ao nosso conhecido <tt>user = User.find(id)</tt>. A biblioteca Ecto organiza usando o pattern de Repository pelo jeito, que obviamente é diferente do pattern ActiveRecord. Programadores Java vão se sentir mais à vontade, mas não é nada difícil (e não, não comecem um flame se é <a href="http://stackoverflow.com/questions/8550124/what-is-the-difference-between-dao-and-repository-patterns"target="_blank" rel="noopener">&ldquo;Repository do DDD ou mero DAO&rdquo;</a>!!)</p>
<p>Antes de falar de &ldquo;plug&rdquo; vamos mexer no arquivo <tt>router.ex</tt>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">PhoenixCrud</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">PhoenixCrud</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="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">PhoenixCrud</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></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;, PhoenixCrud 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>O que eu disse quando executamos o scaffold sobre não compilar é porque precisamos adicionar a seguinte linha ao arquivo anterior:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<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">resources</span> <span class="s2">&#34;/users&#34;</span><span class="p">,</span> <span class="no">UserController</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Novamente: parecido com Rails! Mas aqui tem uma coisa que poderia ter no Rails (e eu não ficaria surpreso de ver isso no Rails 5): escopo de middlewares. Plugs são mais ou menos como nossos Rack Middlewares. Um Pipeline de Middlewares ou Plugs funciona encadeando um filtro de request/response atrás do outro. No nosso caso é encadear Racks (por isso o nome &ldquo;Rack&rdquo; aliás, literalmente, prateleiras uma em cima ou embaixo da outra).</p>
<p>Lembram do projeto <a href="https://github.com/rails-api/rails-api"target="_blank" rel="noopener">Rails-API</a>? Hoje sabemos que ele vai ser incorporado ao Rails 5 mas o Rails-API começa com o próprio Rails 4.2 e desabilitando a maioria das middlewares que não necessárias numa API. Por exemplo, tecnicamente não precisamos processar session ou mensagens flash (mesmo conceito no Phoenix como no Rails) numa API, então a requisição não precisa passar por esses plugs/middlewares.</p>
<p>E de fato, no <tt>router.ex</tt> definimos pipelines separados como escopos, um para <tt>:browser</tt> e outro para <tt>:api</tt> e colocamos nossas rotas específicas para navegação de browser dentro do escopo adequado. Por isso declaramos explicitamente o <tt>resources &ldquo;/users&rdquo;, UserController</tt> como <tt>pipe_through :browser</tt>. E em particular temos o <tt>plug :scrub_params</tt> no controller definido pras actions &ldquo;create&rdquo; e &ldquo;update&rdquo; que é onde são necessários. Esperto!</p>
<p>Sem esticar demais este artigo vejamos agora como é uma view. Em vez de ERB (Embedded RuBy) temos EEX (Embedded EliXir). Em particular vamos ver o arquivo gerado automaticamente no scaffold, <tt>web/templates/user/form.html.eex</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-html" data-lang="html"><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 f.errors != [] 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="p">&lt;</span><span class="nt">label</span><span class="p">&gt;</span>Name<span class="p">&lt;/</span><span class="nt">label</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="err">&lt;</span>%= text_input f, :name, 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><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>%= submit &#34;Submit&#34;, class: &#34;btn btn-primary&#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 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>Praticamente igual, incluindo o padrão de messages por flash. Correndo o risco de ficar repetitivo: se você já gerou scaffold no Rails algumas vezes, vai notar que é muito parecido mesmo. DSL inspirado fortemente no ActionView com helpers como <tt>form_for</tt>, <tt>humanize</tt>, <tt>text_input</tt>, etc. Ou seja, para desenvolvedores front-end de Rails, estamos praticamente em casa, principalmente porque a idéia é usar diretamente pacotes para Brunch.</p>
<p>Por último, algo que pode ser diferente é o conteúdo do diretório <tt>web/views</tt>, como o arquivo <tt>web/views/user_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="n">defmodule</span> <span class="no">PhoenixCrud</span><span class="o">.</span><span class="n">UserView</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">PhoenixCrud</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></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Pelo que entendi, ele dá aos templates em EEX o contexto da aplicação, como variáveis criadas no controller. No Rails se definimos um <tt>@users = User.all</tt> a view pode usar como <tt>for user in @users</tt>. No Phoenix explicitamente declaramos isso pela diretiva <tt>use PhoenixCrud.Web, :view</tt>. Esse &ldquo;:view&rdquo; está definido no arquivo <tt>web/web.ex</tt> neste 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">defmodule</span> <span class="no">PhoenixCrud</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"># Import URL helpers from the router</span>
</span></span><span class="line"><span class="cl">      <span class="n">import</span> <span class="no">PhoenixCrud</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></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 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>Por isso entendemos que ele declara onde ficam os templates, o contexto do controller, os helpers e funcionalidades de HTML como forms.</p>
<p>Então, com esse código todo, o que podemos fazer?</p>
<p>Primeiro, gostaríamos de ter o equivalente aos nossos <tt>rake db:create</tt> e <tt>rake db:migrate</tt> e de fato:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>mix ecto.create
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>E para rodar a aplicação web? Qual o equivalente ao nosso <tt>rails server</tt>?</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>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>Isso vai subir o servidor Cowboy (o equivalente Puma) na porta 4000 em vez de 3000. Daí teremos telas como estas:</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/494/big_Screen_Shot_2015-06-02_at_15.26.26.png" alt="welcome"  loading="lazy" /></p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/495/big_Screen_Shot_2015-06-02_at_15.26.34.png" alt="form"  loading="lazy" /></p>
<p>Uma única coisa que faz sentido para um scaffold é que ele já vem pré-configurado para usar Bootstrap. Mas eu recomendo usar com cuidado (sem muito flame, prefira usar algo como um Bourbon com Compass e Susy).</p>
<p>E como está definido em <tt>lib/phoenix_crud/endpoint.ex</tt> sabemos que ele tem suporte a servir arquivos estáticos, live reloading de código, fora o básico como logger, parser, etc. Veja:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<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">PhoenixCrud</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">:phoenix_crud</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Serve at &#34;/&#34; the static files from &#34;priv/static&#34; directory.</span>
</span></span><span class="line"><span class="cl">  <span class="c1">#</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># You should set gzip to true if you are running phoenix.digest</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># when deploying your static files in production.</span>
</span></span><span class="line"><span class="cl">  <span class="n">plug</span> <span class="no">Plug</span><span class="o">.</span><span class="n">Static</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="ss">at</span><span class="p">:</span> <span class="s2">&#34;/&#34;</span><span class="p">,</span> <span class="ss">from</span><span class="p">:</span> <span class="ss">:phoenix_crud</span><span class="p">,</span> <span class="ss">gzip</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">only</span><span class="p">:</span> <span class="o">~</span><span class="n">w</span><span class="p">(</span><span class="n">css</span> <span class="n">images</span> <span class="n">js</span> <span class="n">favicon</span><span class="o">.</span><span class="n">ico</span> <span class="n">robots</span><span class="o">.</span><span class="n">txt</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Code reloading can be explicitly enabled under the</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># :code_reloader configuration of your endpoint.</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="n">code_reloading?</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">plug</span> <span class="no">Phoenix</span><span class="o">.</span><span class="n">LiveReloader</span>
</span></span><span class="line"><span class="cl">    <span class="n">plug</span> <span class="no">Phoenix</span><span class="o">.</span><span class="n">CodeReloader</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">plug</span> <span class="no">Plug</span><span class="o">.</span><span class="n">Logger</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">plug</span> <span class="no">Plug</span><span class="o">.</span><span class="n">Parsers</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="ss">parsers</span><span class="p">:</span> <span class="o">[</span><span class="ss">:urlencoded</span><span class="p">,</span> <span class="ss">:multipart</span><span class="p">,</span> <span class="ss">:json</span><span class="o">]</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="ss">pass</span><span class="p">:</span> <span class="o">[</span><span class="s2">&#34;*/*&#34;</span><span class="o">]</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="ss">json_decoder</span><span class="p">:</span> <span class="no">Poison</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">plug</span> <span class="no">Plug</span><span class="o">.</span><span class="n">MethodOverride</span>
</span></span><span class="line"><span class="cl">  <span class="n">plug</span> <span class="no">Plug</span><span class="o">.</span><span class="n">Head</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">plug</span> <span class="no">Plug</span><span class="o">.</span><span class="n">Session</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="ss">store</span><span class="p">:</span> <span class="ss">:cookie</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="ss">key</span><span class="p">:</span> <span class="s2">&#34;_phoenix_crud_key&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="ss">signing_salt</span><span class="p">:</span> <span class="s2">&#34;JFgnoLpe&#34;</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">:router</span><span class="p">,</span> <span class="no">PhoenixCrud</span><span class="o">.</span><span class="n">Router</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>
<h2>Conclusão<span class="hx:absolute hx:-mt-20" id="conclusão"></span>
    <a href="#conclus%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Ainda é cedo para dizer como melhor usar o Phoenix mas de cara vejo uma grande oportunidade para desenvolvedores Ruby conseguirem rapidamente criar &ldquo;microsserviços&rdquo; para servir APIs puxando dados de um PostgreSQL. Não cheguei a brincar nem comentar aqui mas na estrutura que mostrei acima existe o diretório <tt>web/channels</tt> para o server-side para Web Sockets e o arquivo vendorizado <tt>web/static/vendor/phoenix.js</tt> que é o client-side Javascript para se conectar nesses channels.</p>
<p>Então para casos de uso como Chats, Push-Notifications ou outras coisas real-time e APIs simples de alta concorrência, talvez seja mais uma excelente opção.</p>
<p>Mas lembrando que embora o Elixir tenha atingido a versão 1.0, o framework Phoenix ainda é bastante jovem (em sua versão 0.13 até o momento deste artigo). Já vi alguns posts de blogs com tutoriais para o Phoenix que mostram código um pouco diferente do que listei neste post e quando forem tentar, talvez já tenha acontecido mais modificações, por isso eu disse que é como acompanhar o Edge Rails.</p>
<p>Não recomendo, claro, fazer uma aplicação para colocar em produção sem ter em mente o comprometimento de que deve precisar fazer manutenções frequentes, para corrigir bugs tanto de funcionamento quanto segurança, que ainda não sabemos se tem ou não (não há nada equivalente a um Brakeman, por exemplo). Mas de qualquer forma, já é possível testar um deployment ao Heroku porque alguém já fez um <a href="https://github.com/HashNuke/heroku-buildpack-elixir"target="_blank" rel="noopener">buildpack</a> pra isso.</p>
<p>Apesar das semelhanças com Ruby até aqui, ele não é nem de longe igual aos paradigmas de Ruby. Então a sintaxe serve de incentivo a rubistas para entender mais sobre os paradigmas específicos de Erlang e patterns do OTP. Mas usar o Phoenix como incentivo para aprender mais da linguagem deve servir o mesmo propósito de usar Rails para aprender mais de Ruby.</p>
]]></content:encoded><category>elixir-e-erlang</category><category>rails</category><category>desenvolvimento-web</category></item><item><title>Web Components é uma Revolução?</title><link>https://akitaonrails.github.io/2014/07/06/web-components-e-uma-revolucao/</link><guid isPermaLink="true">https://akitaonrails.github.io/2014/07/06/web-components-e-uma-revolucao/</guid><pubDate>Sun, 06 Jul 2014 01:09:00 -0300</pubDate><description>&lt;p&gt;Existe uma nova celebridade na cidade, e seu nome é &lt;a href="http://webcomponents.org/"target="_blank" rel="noopener"&gt;&amp;ldquo;Web Components&amp;rdquo;&lt;/a&gt;. Ultimamente muitos apresentam esta nova tecnologia como o &amp;lsquo;Santo Graal&amp;rsquo; que vai resolver todos os problemas da Web. Este artigo não é um apoio incondicional, não é uma crítica negativa irrefutável, mas meramente uma apresentação de perspectivas com o objetivo de dar clareza. Nenhuma discussão pode funcionar onde os gritos mais altos são simplesmente &lt;em&gt;&amp;ldquo;Isso vai revolucionar a Web!&amp;rdquo;&lt;/em&gt; (sem explicar porque) ou &lt;em&gt;&amp;ldquo;O Google e a Mozilla estão por trás então é foda!&amp;rdquo;&lt;/em&gt; (sem comentários).&lt;/p&gt;</description><content:encoded><![CDATA[<p>Existe uma nova celebridade na cidade, e seu nome é <a href="http://webcomponents.org/"target="_blank" rel="noopener">&ldquo;Web Components&rdquo;</a>. Ultimamente muitos apresentam esta nova tecnologia como o &lsquo;Santo Graal&rsquo; que vai resolver todos os problemas da Web. Este artigo não é um apoio incondicional, não é uma crítica negativa irrefutável, mas meramente uma apresentação de perspectivas com o objetivo de dar clareza. Nenhuma discussão pode funcionar onde os gritos mais altos são simplesmente <em>&ldquo;Isso vai revolucionar a Web!&rdquo;</em> (sem explicar porque) ou <em>&ldquo;O Google e a Mozilla estão por trás então é foda!&rdquo;</em> (sem comentários).</p>
<p>Do ponto de vista puramente técnico é muito simples entender o que é um &ldquo;Web Component&rdquo;. Ele é o conjunto de 4 tecnologias de navegadores web.</p>
<ul>
<li>Custom Elements</li>
<li>HTML Imports</li>
<li>Templates</li>
<li>Shadow DOM</li>
</ul>
<p>Colocando em ordem de importância, eu resumiria da seguinte forma:</p>
<ul>
<li>Custom Elements - permite criar tags diferente dos canônicos como &ldquo;body&rdquo;, &ldquo;input&rdquo; e evitar o classes-hell ou div-hell onde tudo é um punhado de divs dentro de divs criando os horrendos macarrões de tags. A idéia é encapsular tudo dentro de tags simples e mais fáceis de manusear.</li>
<li>Shadow DOM - para o fator encapsulamento, precisamos primeiro conseguir eliminar o sangramento de estilos e comportamentos por tudo que existe numa página. Shadow DOM permite criar trechos de nós de elementos de DOM que são independentes e isolados entre si, onde o estilo de um trecho não interfere com outro. Somente o Chrome e Firefox mais novos suportam isso. WebKit/Safari deixou de suportar a implementação incompleta que tinham (mas talvez voltem) e o IE, para variar, nunca nem tentou implementar isso. Para funcionar na maioria dos navegadores existem projetos como o Polymer/Platform e X-Tag para servir como &ldquo;polyfill&rdquo; para navegadores legados.</li>
<li>Templates - como o nome diz, para criar componentes dinâmicos, é importante ter trechos de DOM inertes, diferentes de somente uma string, para conseguir manipular e instanciar conforme necessário. Por exemplo, linhas de uma tabela dinâmica. Existem várias bibliotecas de templates, como Mustache, mas para Web Components funcionar, era importante ter um padrão.</li>
<li>HTML Imports - e uma vez tendo um trecho de DOM isolado, com estilos e comportamentos independentes de outros componentes, agora precisamos empacotar isso. E para isso servem esses imports que basicamente é um arquivo HTML que declara os arquivos CSS e Javascript que ele depende, e o HTML Import sabe como carregar tudo de uma vez. Aqui fica uma das minhas maiores preocupações - que vou tentar explicar mais para o fim.</li>
</ul>
<p>Existem já dezenas de artigos e documentação de como é um Web Component, como construir um do zero e onde encontrar componentes. Não é foco deste artigo explicar os detalhes técnicos de um Web Component.</p>
<p>Por si só, devo dizer que são tecnologias interessantes que todo bom nerd gosta de brincar. Porém não deveria ser novidade que só porque alguma coisa é tecnicamente interessante, por si só, não garante absolutamente nada. A principal pergunta é: <em>&ldquo;o que eu ganho com isso?&rdquo;</em> Antes de responder a essa pergunta, toda tecnologia é somente uma solução procurando um problema.</p>
<p>Infelizmente nem eu, nem ninguém, temos como prever o futuro, então neste artigo quero avaliar qualquer proposta baseado no que temos no presente, relembrar o passado e o que isso pode representar no curto prazo. O longo prazo deixo para os profetas e quem gosta de apostar em loteria. Então vou tentar quebrar alguns dos grandes problemas do desenvolvimento Web que temos hoje.</p>
<h2>O Problema do HTML/CSS macarrônicos<span class="hx:absolute hx:-mt-20" id="o-problema-do-htmlcss-macarrônicos"></span>
    <a href="#o-problema-do-htmlcss-macarr%c3%b4nicos" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A forma de desenhar uma página Web mudou bastante nos últimos 20 anos. As tecnologias básicas ainda são as mesmas: HTML 5 para estruturar um documento. CSS 3 para estilizar os elementos nesse documento. Javascript para adicionar comportamento. Acho que a esta altura todos já sabemos que devemos separar estilização/apresentação da estrutura e conteúdo. Também sabemos que não devemos nem usar tabelas para tudo e nem usar divs com float para tudo.</p>
<p>Passamos pela era da <a href="http://semanticweb.org/wiki/Main_Page"target="_blank" rel="noopener">Web Semântica</a> e <a href="http://www.smashingmagazine.com/2011/01/12/guidelines-for-responsive-web-design/"target="_blank" rel="noopener">Web Responsiva</a>. Quando os navegadores implementaram tables no <a href="http://www.barrypearson.co.uk/articles/layout_tables/history.htm"target="_blank" rel="noopener">Netscape Navigator 1.1 e IE 2.0 em 1995 e 1996</a> ainda não se falava em acessibilidade e semântica apareceriam a partir de 1997 com o HTML 3.2. Estávamos criando a Web e usando ao mesmo tempo, como hoje as implementações apareciam antes de se ter uma especificação mais geral. A W3C é lenta não é de hoje.</p>
<p>Acho que a evangelização de Responsive Web também merece um &ldquo;rant&rdquo; mas fica para outro artigo.</p>
<p>Mesmo assim ainda somos reféns de códigos como deste exemplo de tabs do framework <a href="http://getbootstrap.com/components/#input-groups"target="_blank" rel="noopener">Bootstrap</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-HTML" data-lang="HTML"><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-tabs&#34;</span> <span class="na">role</span><span class="o">=</span><span class="s">&#34;tablist&#34;</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="na">class</span><span class="o">=</span><span class="s">&#34;active&#34;</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;#&#34;</span><span class="p">&gt;</span>Home<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">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;#&#34;</span><span class="p">&gt;</span>Profile<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">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;#&#34;</span><span class="p">&gt;</span>Messages<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></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-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 vejamos um trecho para tabs mas usando um web component do novíssimo <a href="http://www.polymer-project.org/components/paper-elements/demo.html#paper-tabs"target="_blank" rel="noopener">Polymer/Paper</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-HTML" data-lang="HTML"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">paper-tabs</span> <span class="na">selected</span><span class="o">=</span><span class="s">&#34;0&#34;</span> <span class="na">selectedindex</span><span class="o">=</span><span class="s">&#34;0&#34;</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;paper_tabs&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">paper-tab</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;tab_home&#34;</span> <span class="na">active</span><span class="p">&gt;</span>Home<span class="p">&lt;/</span><span class="nt">paper-tab</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">paper-tab</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;tab_profile&#34;</span><span class="p">&gt;</span>Profile<span class="p">&lt;/</span><span class="nt">paper-tab</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">paper-tab</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;tab_messages&#34;</span><span class="p">&gt;</span>Messages<span class="p">&lt;/</span><span class="nt">paper-tab</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">paper-tabs</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>Esse é o resultado da primeira tecnologia de Web Components: <a href="http://www.html5rocks.com/en/tutorials/webcomponents/customelements/"target="_blank" rel="noopener">Custom Elements</a>. De fato isso acrescenta uma clareza enorme. No primeiro exemplo o que temos é:</p>
<blockquote>"Uma lista não ordenada com classes nav e nav-tags e dentro delas itens de lista."</blockquote>
<p>Se você souber o que são as classes &ldquo;nav&rdquo; e &ldquo;nav-tabs&rdquo; vamos saber que se trata de uma lista de tabs. No caso do exemplo do Paper podemos ler claramente:</p>
<blockquote>"Um conjunto de tabs, implementado com Paper."</blockquote>
<p>Isto é um exemplo pequeno mas imaginando como páginas Web são complexas, tirar a ambiguidade de para que servem cada conjunto de tags numa árvore infinita de tags é muito importante. Perdemos muito tempo em tentativa e erro de retirar pedaços e tentando inserir outros pedaços no meio da árvore de HTML e decifrando nomes de classes CSS.</p>
<p>Na prática, sem ter Custom Elements para tornar as tags mais amigáveis, outra forma é encapsular a inicialização usando um marcador simples numa tag HTML (como uma classe CSS ou ID) e ativar o resto via Javascript. É como todo plugin JQuery funciona. Um exemplo simples é como encapsulamos o Google Maps com o plugin <a href="http://gmap3.net/en/catalog/9-map/map-32"target="_blank" rel="noopener">Gmap3</a> do JQuery:</p>
<div class="hextra-code-block 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">div</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;my-map&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nx">$</span><span class="p">(</span><span class="s2">&#34;#my-map&#34;</span><span class="p">).</span><span class="nx">gmap3</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nx">map</span><span class="o">:</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">address</span><span class="o">:</span><span class="s2">&#34;Cupertino, CA&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">options</span><span class="o">:</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">zoom</span><span class="o">:</span><span class="mi">4</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">mapTypeId</span><span class="o">:</span> <span class="nx">google</span><span class="p">.</span><span class="nx">maps</span><span class="p">.</span><span class="nx">MapTypeId</span><span class="p">.</span><span class="nx">SATELLITE</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">mapTypeControl</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">mapTypeControlOptions</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nx">style</span><span class="o">:</span> <span class="nx">google</span><span class="p">.</span><span class="nx">maps</span><span class="p">.</span><span class="nx">MapTypeControlStyle</span><span class="p">.</span><span class="nx">DROPDOWN_MENU</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="nx">navigationControl</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">scrollwheel</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">streetViewControl</span><span class="o">:</span> <span class="kc">true</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">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E outro exemplo com a alternativa com o <a href="http://polymerlabs.github.io/google-map/components/google-map/"target="_blank" rel="noopener">Web Component Google-Map</a> (lembrando que esta versão é mais simples do que o plugin Gmap3):</p>
<div class="hextra-code-block 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">google-map</span> <span class="na">latitude</span><span class="o">=</span><span class="s">&#34;37.77493&#34;</span> <span class="na">longitude</span><span class="o">=</span><span class="s">&#34;-122.41942&#34;</span> <span class="na">fitToMarkers</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">google-map-marker</span> <span class="na">latitude</span><span class="o">=</span><span class="s">&#34;37.779&#34;</span> <span class="na">longitude</span><span class="o">=</span><span class="s">&#34;-122.3892&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="na">draggable</span><span class="o">=</span><span class="s">&#34;true&#34;</span> <span class="na">title</span><span class="o">=</span><span class="s">&#34;Go Giants!&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">google-map-marker</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">google-map-marker</span> <span class="na">latitude</span><span class="o">=</span><span class="s">&#34;37.777&#34;</span> <span class="na">longitude</span><span class="o">=</span><span class="s">&#34;-122.38911&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">google-map-marker</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">google-map</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>Estamos movendo parte do que faríamos via Javascript de volta ao HTML. Mas somente isso não é suficiente e já sabemos disso. A tecnologia de &ldquo;Cascading Style Sheet versão 3&rdquo;, comumente conhecida como CSS 3 até hoje permanece arcaica. Felizmente inventamos uma forma de tornar os malditos macarrões de CSS gerenciáveis. Agradecimentos são devidos à <a href="http://www.hamptoncatlin.com"target="_blank" rel="noopener">Hampton Catlin</a>, um grande rubista que - em <strong>2007</strong> - inventou a <a href="http://sass-lang.com/"target="_blank" rel="noopener">linguagem de script SASS</a>, que é um &ldquo;super-set&rdquo; de CSS e gera CSS 3, adicionando variáveis, nesting, partials, import, mixins, extensão/herança, operadores.</p>
<p>SASS foi a primeira linguagem de pré-processamento que acelerou a idéia da possibilidade de novas formas de gerenciar assets Web como CSS. Depois dele vieram LESS e toda uma gama de frameworks e bibliotecas como <a href="http://singularity.gs/"target="_blank" rel="noopener">Singularity</a>, <a href="http://neat.bourbon.io/"target="_blank" rel="noopener">Bourbon Neat</a>, <a href="http://susy.oddbird.net/"target="_blank" rel="noopener">Susy</a>, <a href="http://foundation.zurb.com/"target="_blank" rel="noopener">Zurb Foundation</a>, <a href="http://compass-style.org/"target="_blank" rel="noopener">Compass</a>. Foi uma pena terem escrito o Twitter Bootstrap original em <a href="http://wordsbyf.at/2012/03/08/why-less/"target="_blank" rel="noopener">LESS em vez de SASS</a>, mas acho que o projeto <a href="http://libsass.org/"target="_blank" rel="noopener">LibSass</a> (SASS escrito em C) ainda não existia. Mas pelo próprio exemplo acima fica claro porque você deveria escolher outro framework em vez de Bootstrap.</p>
<p>Porém SASS não foi a primeira vez que alguém criou um pré-processador para a Web. Provavelmente a primeira foi a <a href="http://en.wikipedia.org/wiki/Wiki_markup"target="_blank" rel="noopener">Wiki Markup</a> implementada junto com o primeiro Wiki, o <a href="http://en.wikipedia.org/wiki/WikiWikiWeb"target="_blank" rel="noopener">WikiWikiWeb</a> de Ward Cunningham em 1995. Depois disso ainda tivemos o MediaWiki da Wikipedia até chegarmos atualmente a pré-processadores de HTML como Markdown, que praticamente usamos sem pensar em comentários de Github.</p>
<p>No mundo Rails já tentamos melhorar o HTML também, com pré-processadores como o HAML (do próprio Catlin) ou outros mais recentes como <a href="http://slim-lang.com/"target="_blank" rel="noopener">Slim</a>. Mas nesse caso ainda não estamos melhorando o HTML tão drasticamente como SASS faz com CSS, por isso ainda não há uma boa alternativa e Custom Elements é o mais próximo de uma boa alternativa neste momento.</p>
<p>Estou trazendo estes pontos porque Web Components não declara nenhum pré-processador de CSS como padrão, por convenção. Assumimos CSS 3 puro. Não é um problema, mas se estamos falando de reuso, atualmente a melhor forma é usando mixins de SASS encapsuladas em bibliotecas como Compass ou Bourbon.</p>
<h2>O Problema do Sangramento de Estilos e Comportamentos<span class="hx:absolute hx:-mt-20" id="o-problema-do-sangramento-de-estilos-e-comportamentos"></span>
    <a href="#o-problema-do-sangramento-de-estilos-e-comportamentos" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Este é um dos aspectos mais irritantes de desenvolvimento front-end depois da compatibilidade de navegadores: como organizar as milhares de linhas de CSS? (seja ele puro, escrito em SASS ou LESS).</p>
<p>Antes de continuar, se nunca ouviu falar de <a href="http://markdaggett.com/blog/2011/12/04/css-anti-patterns/"target="_blank" rel="noopener">CSS Anti-patterns</a>, leia a respeito antes. E também que todos já pelo menos ouviram falar nas diversas metodologias para organizar CSS (<a href="http://www.smashingmagazine.com/2011/12/12/an-introduction-to-object-oriented-css-oocss/"target="_blank" rel="noopener">OOCss</a> , <a href="http://www.smashingmagazine.com/2012/04/20/decoupling-html-from-css/"target="_blank" rel="noopener">SMACSS</a>, <a href="http://csswizardry.com/2013/01/mindbemding-getting-your-head-round-bem-syntax/"target="_blank" rel="noopener">BEM</a>). Eu honestamente não usaria ACSS e me foram dados bons argumentos para preferir BEM.</p>
<p>Essa introdução foi somente porque também não sou ainda um bom desenvolvedor front-end mas até aqui é o mínimo para começar. Vamos assumir que já passamos desses pontos também. Meu raciocínio começa pelo seguinte caso de uso: <em>&ldquo;quero criar CSS para um elemento visual, composto de diversos tags, e que não deve se deformar aleatoriamente por estilo de fora e nem deformar outros elementos ao redor.&rdquo;</em> Seria o caso de um Widget ou o que poderia ser um &ldquo;Web Component&rdquo;, ao pé-da-letra.</p>
<p>Passamos anos fazendo <a href="http://codeutopia.net/blog/2012/05/26/best-practices-for-building-embeddable-widgets/"target="_blank" rel="noopener">Widgets</a>. As duas grandes formas são usando um iframe com <a href="http://blog.teamtreehouse.com/cross-domain-messaging-with-postmessage"target="_blank" rel="noopener">postMessage()</a> ou zerar os estilos de um trecho do DOM com <a href="https://github.com/premasagar/cleanslate"target="_blank" rel="noopener">Cleanslate</a> e garantir que você está isolando seu CSS e Javascript usando namespaces e torcendo pra acidentalmente não sobrar nada global no meio do caminho ou outro desenvolvedor sobrescrever o que você fez.</p>
<p>Isso mudou em 2012 quando surgiu a implementação de <a href="http://blog.teamtreehouse.com/working-with-shadow-dom"target="_blank" rel="noopener">Shadow DOM</a>. Os novos elementos do HTML 5 como &ldquo;audio&rdquo; ou &ldquo;video&rdquo; ou &ldquo;range&rdquo; foram criados dessa forma em muitos navegadores. Em resumo mencionamos que uma página Web é uma árvore de tags, essa árvore que chamamos de Document Object Model ou &ldquo;DOM&rdquo;. Imagine se pudéssemos isolar um trecho de nós, uma sub-árvore, que não fosse influenciada pelos estilos externos e cujos estilos internos não &ldquo;vazassem&rdquo; para fora. Para isso criamos um novo elemento, um &ldquo;ShadowRoot&rdquo; que delimita uma sub-árvore isolada e independente e que, para todos os propósitos, não aparece no DOM principal, mas tem sua própria raíz de DOM isolada. Esse é o conceito.</p>
<p>No <a href="http://caniuse.com/shadowdom"target="_blank" rel="noopener">momento deste artigo</a>, somente o Chrome a partir da versão 25 (hoje no 35), o Firefox a partir da versão 31 (hoje no 30) e Ópera versão 15 (hoje no 22) suportam Shadow DOM. A Apple <a href="http://trac.webkit.org/changeset/164131"target="_blank" rel="noopener">retirou o suporte do WebKit</a> em fevereiro. A Microsoft sequer <a href="http://connect.microsoft.com/IE/feedback/details/793965/ie11-feature-request-support-for-shadow-dom"target="_blank" rel="noopener">considerou começar a implementar</a> ainda.</p>
<p><a href="http://www.html5rocks.com/en/tutorials/webcomponents/shadowdom/"target="_blank" rel="noopener">Shadow DOM</a> não é obrigatório para criar Custom Elements, mas é um recurso interessante e importante para atingir o objetivo de componentes Web independentes e isolados que não precisem recorrer a recursos como reiniciar o CSS de uma sub-árvore com artifícios como Cleanslate. De qualquer forma, seja via as convenções listadas acima e outras, ou seja via Shadow DOM, hoje temos como criar um nível razoável de isolamento e facilidade de manutenção. O problema é que isso não é inerente a HTML 5 e CSS 3, mas uma camada acima que não tem um consenso geral ainda.</p>
<h2>O Problema de Reusar Elementos<span class="hx:absolute hx:-mt-20" id="o-problema-de-reusar-elementos"></span>
    <a href="#o-problema-de-reusar-elementos" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Num mundo onde fazíamos praticamente tudo via server-side, esse era um problema praticamente resolvido. Seja com Rails e ERB/HAML/Slim, seja com templates de Django, seja com Blade em Laravel, seja com Razor em Asp.Net. Porém hoje temos aplicações mistas, onde deferimos muita lógica para a camada do navegador usando mais Javascript. Em vez de devolver pedaços prontos de HTML, agora devolvemos dados semi-estruturados em JSON e deixamos o Javascript criar os elementos no DOM.</p>
<p>Significa que no navegador temos um conjunto de dados e precisamos de uma forma de declarar os elementos de DOM onde vamos aplicar esses dados antes de instanciar o trecho no DOM. Do mundo Ruby surgiu uma das formas mais populares, <a href="http://mustache.github.io/"target="_blank" rel="noopener">Mustache</a>, criado pelo co-fundador do Github, <a href="https://github.com/defunkt"target="_blank" rel="noopener">Chris Wanstrath</a>. Depois outro rubista, <a href="https://github.com/wycats"target="_blank" rel="noopener">Yehuda Katz</a>, trouxe outra alternativa com o <a href="http://handlebarsjs.com/"target="_blank" rel="noopener">Handlebars</a>. A idéia geral é criar um elemento &ldquo;script&rdquo; e dentro dela colocar templates usando a notação do Mustache. Então um Javascript pode pegar o conteúdo string dessa tag script e criar o elemento no DOM depois de mesclar com os dados que quiser.</p>
<p>Está claro que estamos fazendo uma &ldquo;gambiarra&rdquo; elegante. A parte não tão elegante é ter que injetar HTML usando &ldquo;innerHTML&rdquo;, mas funciona muito bem. O outro problema é controlar manualmente coisas como imagens e javascripts que um elemento pode precisar mas antes dele ser usado, ou seja, talvez sem necessidade. Para isso precisamos controlar via Javascript.</p>
<p>Na definição de Web Components foi criado um novo elemento para resolver alguns desses problemas, o <a href="http://www.html5rocks.com/en/tutorials/webcomponents/template/"target="_blank" rel="noopener">&ldquo;template&rdquo;</a>. Sua definição é bem recente, de fevereiro de 2014. Serve basicamente o mesmo propósito de colocar um template dentro de uma tag &ldquo;script&rdquo; mas em vez de ser somente um string injetável ele realmente é uma sub-árvore de DOM &ldquo;inerte&rdquo;, que podemos manipular os nós via Javascript antes de ativá-lo ao inserir no DOM principal.</p>
<p>Por não ser mais um trecho de texto, perdemos a função do Mustache e do Handlebar de facilmente mesclar dados e inclusive ter alguma lógica como condicionais e loops. Em vez disso precisamos manipular os nós via javascript, fazendo coisas como:</p>
<div class="hextra-code-block 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">t</span><span class="p">.</span><span class="nx">content</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">&#39;img&#39;</span><span class="p">).</span><span class="nx">src</span> <span class="o">=</span> <span class="s1">&#39;logo.png&#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>Em vez de ter:</p>
<div class="hextra-code-block 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">img</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;{{minha-imagem}}&#34;</span> <span class="na">alt</span><span class="o">=</span><span class="s">&#34;great image&#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>Neste caso de templates eu diria que demos 2 passos pra trás e 1 para frente. Provavelmente dentro de um &ldquo;HTML template&rdquo; vamos precisar ter um Mustache para completar o processo? Admito que não acompanho a especificação mas pelo pouco que vi até agora não me parece que a especificação W3C de HTML template resolve o problema que bibliotecas como Mustache, Handlebars ou <a href="https://github.com/tildeio/htmlbars"target="_blank" rel="noopener">HTMLBars</a> resolvem hoje.</p>
<p>E não estou nem mencionando sobre Binding. Pelo que entendi isso não está na especificação, mas implementado em projetos como o <a href="http://mdv.googlecode.com/svn/trunk/docs/design_intro.html"target="_blank" rel="noopener">Google MDV</a> que agora está integrado ao <a href="http://www.polymer-project.org/docs/polymer/databinding.html"target="_blank" rel="noopener">Data Binding e Event Mapping do Polymer</a> como <a href="https://github.com/Polymer/TemplateBinding"target="_blank" rel="noopener">TemplateBinding</a>.</p>
<h2>O Problema de Reusar &ldquo;Componentes&rdquo;<span class="hx:absolute hx:-mt-20" id="o-problema-de-reusar-componentes"></span>
    <a href="#o-problema-de-reusar-componentes" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Definindo &ldquo;Web Components&rdquo; como, um conjunto de HTML, CSS, Javascript que funciona em independência e isolamento, finalmente sobra o problema de &ldquo;empacotar&rdquo; esse conjunto. E agora entra a <a href="http://w3c.github.io/webcomponents/spec/imports/"target="_blank" rel="noopener">nova especificação de HTML Imports</a> de Julho de 2014.</p>
<p>Na prática, um HTML Import é nada mais do que um arquivo HTML &ldquo;normal&rdquo; com links para outros arquivos como stylesheets, imagens, javascripts e outros assets. Novamente, não vou descrever todos os detalhes e para aprender mais leia o artigo <a href="http://www.html5rocks.com/en/tutorials/webcomponents/imports/"target="_blank" rel="noopener">Why imports?</a>.</p>
<p>Atualmente, se precisamos usar algum conjunto desses, como um componente de Bootstrap, por exemplo, precisamos manualmente linkar para os stylesheets na ordem correta e fazer algo como:</p>
<div class="hextra-code-block 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">head</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">&#34;stylesheet&#34;</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;bootstrap.css&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">&#34;stylesheet&#34;</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;fonts.css&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  ...
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">head</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">body</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  ...
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">script</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;jquery.js&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><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;bootstrap.js&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><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;bootstrap-tooltip.js&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><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;bootstrap-dropdown.js&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">body</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>Com HTML Imports podemos fazer simplesmente:</p>
<div class="hextra-code-block 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">head</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">&#34;import&#34;</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;bootstrap.html&#34;</span><span class="p">&gt;</span>
</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></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-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 problema imediato de algo como Web Components é que qualquer pequena página pode ter rapidamente dezenas de Imports, cada import requerendo outros arquivos e potencialmente triplicando ou mais a quantidade de chamadas pela rede. Quando tivermos algo melhor como HTTP 2.0 ou outro processo de <a href="http://stackoverflow.com/questions/10480122/difference-between-http-pipeling-and-http-multiplexing-with-spdy"target="_blank" rel="noopener">pipelining e multiplexing como com o protocolo SPDY</a> talvez não tenhamos que nos preocupar com isso, mas com o HTTP 1.1 que temos hoje precisamos ainda pensar em como economizar ao máximo chamadas de rede para pedir assets.</p>
<p>O mundo Rails trouxe pela primeira vez uma implementação de um processo completo e automatizado para concatenar e minificar stylesheets, javascript e sprites com o <a href="https://github.com/sstephenson/sprockets"target="_blank" rel="noopener">Sprockets</a> que <a href="https://github.com/sstephenson"target="_blank" rel="noopener">Sam Stephenson</a> - também criador do Prototype - iniciou em fevereiro de 2009 (obviamente os componentes que podem ser usados pelo Sprockets, como compressor, já existiam). Finalmente o Sprockets foi adicionado ao Rails 3.1 lançado em Agosto de 2011 como <a href="http://www.akitaonrails.com/2012/07/01/asset-pipeline-para-iniciantes"target="_blank" rel="noopener">&ldquo;Asset Pipeline&rdquo;</a>.</p>
<p>Hoje em dia o Sprockets continua sendo uma das opções mais completas e outros frameworks correram para criar suas próprias versões, como o <a href="https://github.com/d-i/half-pipe"target="_blank" rel="noopener">&ldquo;connect-assets&rdquo;</a> para Node.js, diversas receitas para Grunt e <a href="http://blog.carbonfive.com/2014/05/05/roll-your-own-asset-pipeline-with-gulp/"target="_blank" rel="noopener">Gulp</a>) e opções para <a href="http://grails.org/plugin/asset-pipeline"target="_blank" rel="noopener">Grails</a>, <a href="http://getcassette.net/"target="_blank" rel="noopener">Cassette/ASP.NET MVC</a>, <a href="https://github.com/cyberdelia/django-pipeline"target="_blank" rel="noopener">Pipeline Django</a>.</p>
<p>No mundo de Web Components foi imediatamente criado o projeto <a href="http://www.polymer-project.org/articles/concatenating-web-components.html"target="_blank" rel="noopener">Vulcanize</a> ou no caso do mundo Rails podemos adicionar o processo ao próprio Sprockets com o projeto <a href="http://www.akitaonrails.com/2014/06/29/usando-polymer-com-rails"target="_blank" rel="noopener">Emcee</a>, que foi o tema do meu post anterior. A idéia é pegar os HTML imports e concatená-los num único arquivo, incluindo tornar os links para stylesheets e javascripts externos como trechos inline num único HTML. Nem o Vulcanize e nem o Emcee atualmente minificam seus conteúdos, no entanto.</p>
<p>Note que imports é apenas uma parte do problema. Nem vamos entrar no problema de gerenciamento de versões (problema que projetos como Bower, NPM, Bundler no Ruby, PHP Composer, NuGet, Gradle, Pip, etc foram criados para resolver). O que acontece com componentes que dependem de versões específicas de outro componente? E bibliotecas internas como JQuery que um componente pode precisar? Ainda não sei o que acontece nesses casos.</p>
<h2>Web Components é uma &ldquo;Revolução&rdquo;?<span class="hx:absolute hx:-mt-20" id="web-components-é-uma-revolução"></span>
    <a href="#web-components-%c3%a9-uma-revolu%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Tudo isso dito, vamos revisar:</p>
<ul>
<li>Podemos criar conjuntos de tags isolados com Shadow DOM, com seus próprios estilos e comportamentos que não vazam e não automaticamente herdam estilos e comportamentos do DOM principal, a técnica é usar Shadow DOM (ou uma das formas antigas com Cleanslate, criação de elementos com Javascript, IFrame)</li>
<li>Podemos definir trechos inertes de HTML que podem ser instanciados via Javascript para dentro do DOM. Com projetos como TemplateBinding recuperamos algo como HTMLBars e podemos mesclar dados com o template. A técnica é usar HTML template e Polymer (ou uma das formas antigas com Mustache, Handlebars, HTMLBars).</li>
<li>Uma vez definido nosso &ldquo;widget&rdquo; ou &ldquo;componente&rdquo;, podemos encapsulá-lo dentro de uma embalagem mais amigável usando Custom Elements (ou uma das formas antigas como plugins de JQuery fazem com Javascript para encapsular).</li>
<li>E uma vez montado o Widget/Componente podemos empacotar tudo num único HTML Import para podermos resusar facilmente dentro de outros HTML (em vez da forma antiga de declarar manualmente os arquivos de dependência)</li>
</ul>
<p>Vocês devem ter notado que do ponto de vista de um desenvolvedor que apenas consome tais componentes/widgets, o processo em si é uma melhoria mas não é tão diferente quanto utilizar plugins de JQuery. De fato é uma passo adiante, oficializando o que antes era &ldquo;gambiarra&rdquo; em alguma coisa um pouco melhor (Custom Elements em vez de Javascript, Shadow DOM em vez de IFrame, HTML Template em vez de string em tag Script).</p>
<p>E aqui vem o maior desafio ao conjunto de tecnologias de Web Components: ele é definitivamente melhor. Só que não é disruptivo. Ao usuário final, o que ele &ldquo;vê&rdquo; e &ldquo;interage&rdquo; numa página web no navegador será exatamente idêntico. Significa que o usuário final tem muito pouco a se beneficiar da tecnologia. Portanto ele não é o público-alvo.</p>
<p>O público-alvo é o desenvolvedor front-end que, em vez de buscar um plugin JQuery - por exemplo - vai buscar um Web Component e a forma de utilizá-los não será muito diferente. Como ele não tem a motivação de melhorar muita coisa a seu público-alvo, ele tem muito pouco incentivo para mudar sua forma de trabalho.</p>
<p>O raciocínio é simples: quero colocar um Carrossel na minha página. Existem centenas, <a href="http://www.jssor.com/"target="_blank" rel="noopener">Jssor Slider</a>, <a href="http://sorgalla.com/jcarousel/"target="_blank" rel="noopener">jCarousel</a>, <a href="http://kenwheeler.github.io/slick/"target="_blank" rel="noopener">Slick - the last carousel you&rsquo;ll ever need</a>. E para Web Components? Pelo menos no momento deste artigo só encontrei o <a href="https://github.com/addyosmani/polymer-ui-carousel"target="_blank" rel="noopener">Polymer-ui-carousel</a>, que não chega aos pés dos antecessores. Qual você acha que o desenvolvedor front-end vai escolher?</p>
<p>Existem dois repositórios de componentes públicos (aliás, não entendi porque existem dois, um projeto como esse não ganha nada nascendo já fragmentado): o <a href="http://component.kitchen/"target="_blank" rel="noopener">Component Kitchen</a> com cerca de 276 componentes e o <a href="http://customelements.io/"target="_blank" rel="noopener">Custom Elements</a> com 327 componentes. Como todo repositório inicial, a maioria deles não é de grande serventia. São mais demonstrações e provas de conceito. Sinceramente, alguém se dar ao trabalho para fazer um componente de Pokémon tem que ser só um tech-demo. É inútil. E você pode contar com coisas redundantes e/ou desnecessárias como botões de Facebook, gravatar, botão de Github, coisas que um <a href="http://www.addthis.com/"target="_blank" rel="noopener">AddThis</a> já tem resolvido. Claro, não quero ser injusto e dizer que Web Components não tem utilidade só porque a primeira geração de componentes não é interessante. Essas coisas levam tempo para amadurecer, como qualquer tecnologia.</p>
<p>Para que a estratégia funcione ou a tecnologia em si é <string>disruptiva</string> - o que acabamos de ver que não é, é apenas <strong>evolutiva</strong> -, ou é necessário um pacote completo para ser candidato a <a href="http://en.wikipedia.org/wiki/Killer_application"target="_blank" rel="noopener">&ldquo;killer-app&rdquo;</a>. Voltamos ao que disse antes sobre uma solução procurando um problema. Sem um verdadeiro killer-app-blockbuster é muito difícil conseguir tração e coesão de um ecossistema. No caso do Ruby isso foi o Rails. No caso do Go parece que é o Docker. No caso do Node foi o halo-effect do Google Chrome evoluindo o V8.</p>
<p>Para isso o Google criou o <a href="http://www.polymer-project.org/components/paper-elements/demo.html#core-toolbar"target="_blank" rel="noopener">Polymer/Paper</a> que define um conjunto coeso e visualmente integrado (via filosofia gráfica e interativa do <a href="http://www.google.com/design/"target="_blank" rel="noopener">Material Design</a> que, assumindo que &ldquo;pegue&rdquo;, também tem a vantagem de dar ao usuário final um &ldquo;eye-candy&rdquo; que justifique sua adoção). É uma proposição &ldquo;tudo ou nada&rdquo;, embora você possa pegar pedaços dela, a intenção é fazer aplicações/sites usando inteiramente o Polymer.</p>
<p>O projeto <a href="http://www.polymer-project.org/"target="_blank" rel="noopener">Polymer</a> é ambicioso e somente uma corporação como o Google poderia fazer algo assim tão rápido (dado que metade das especificações de tecnologia de Web Components como HTML Imports e HTML template são deste ano de 2014). Ao contrário do que deveria parecer, a arquitetura não é simples e com o pouco suporte de navegadores a única estratégia é criar Polyfills, implementações (&ldquo;quasi&rdquo;-completas) para tudo que navegadores &ldquo;legados&rdquo; mas que muita gente usa como IE abaixo de 10, Safari e Mobile Safari. Para isso serve o <a href="http://www.polymer-project.org/docs/start/platform.html"target="_blank" rel="noopener">Polymer/Platform</a>.</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/413/polymer-architecture.png" alt="Arquitetura do Polymer"  loading="lazy" /></p>
<p>Só que já passamos do ponto de somente usar plugins JQuery, agora migramos para a era dos one-page-apps via Javascript que consome endpoints REST que retornam JSON. Estamos falando somente do ponto de vista de componentes, mas uma aplicação é maior do que isso. Para quem vem do mundo ASP.NET MVC, Rails, Django, falamos muito sobre o conceito &ldquo;Web MVC&rdquo; (Model-View-Controller) ou mais corretamente, <a href="http://en.wikipedia.org/wiki/Model_2"target="_blank" rel="noopener">MVC Model-2</a> da Sun, definido para J2EE.</p>
<p>Muitos nem sabem que usam outro modelo, o <a href="http://en.wikipedia.org/wiki/Model%e2%80%93view%e2%80%93presenter"target="_blank" rel="noopener">MVP</a> (Model-View-Presenter) presente em ASP.NET (antigo), Window.Forms, <a href="http://www.gwtproject.org/?csw=1"target="_blank" rel="noopener">GWT</a> (Google Web Toolkit, lembram? Também do Google!). E, finalmente, também da Microsoft chegamos ao <a href="http://en.wikipedia.org/wiki/Model_View_ViewModel"target="_blank" rel="noopener">MVVM</a> (Model View View Model), uma evolução de MVP com MVC2, mais conhecido pelos seus conceitos de Data Binding 2-way de elementos do DOM com models puxados via Javascript. Agora temos a categoria que vocês já devem ter conhecido com <a href="http://knockoutjs.com/"target="_blank" rel="noopener">Knockout.js</a>, <a href="https://angularjs.org/"target="_blank" rel="noopener">Angular.js</a>, <a href="http://emberjs.com/"target="_blank" rel="noopener">Ember</a>. Estamos falando de mais do que o jeito MVP de arrastar componentes a um canvas e adicionar eventos de callback, mas um framework completo que lida com Models, Views, Rotas.</p>
<p>O Yehuda Katz escreveu sobre <a href="https://gist.github.com/wycats/9144666b0c606d1838be"target="_blank" rel="noopener">integrar o Ember com Web Components</a> em Maio e ele explicita muitos detalhes que vale a pena ler a respeito. Segundo esta <a href="https://groups.google.com/forum/#!msg/polymer-dev/4RSYaKmbtEk/uYnY3900wpIJ"target="_blank" rel="noopener">thread no Google Groups</a>, parece que o projeto Angular 2.0 deve também se integrar com Web Components e Polymer. Quando estes projetos e outros frameworks se unirem ao esforço Polymer provavelmente vamos ver o real valor dos Web Components.</p>
<p>Polymer é o projeto principal. Web Components é o conjunto de mecanismos, o meio para atingir esse objetivo. E é um problema clássico de ovo e galinha: os navegadores diferentes de Chrome precisam se equiparar e seguir a vontade do Google. Os polyfills, via Polymer/Platform e <a href="http://www.x-tags.org/blog"target="_blank" rel="noopener">Mozilla X-Tag</a> vão suprir o que falta (Mutation Observers, Shadow DOM, etc) como o JQuery fez no passado. Mas para os navegadores tracionarem nessa direção os desenvolvedores precisam entrar no barco, e para isso somente um pacote completo como Polymer/Paper e, se possível, aliado a frameworks como Ember e Angular podem fazer isso acontecer, e não pequenos componentes-demonstração.</p>
<h2>A História se Repete<span class="hx:absolute hx:-mt-20" id="a-história-se-repete"></span>
    <a href="#a-hist%c3%b3ria-se-repete" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Já vimos essa história acontecer antes. Durante o <a href="http://en.wikipedia.org/wiki/Browser_wars"target="_blank" rel="noopener">Browser Wars</a> muitos apenas vão se lembrar que a Microsoft conspirou ativamente para destruir a Netscape, culminando no processo que quase quebrou a Microsoft em 3 no ano 2000 e a consequência final que foi a sobra do amaldiçoado Internet Explorer 6 que, apesar de ter sido um excelente navegador em sua era, ele não deveria nunca ter durado mais de uma década em uso.</p>
<p>Muitos talvez não lembrem que isso também gerou muitas novas tecnologias, para listar algumas:</p>
<ul>
<li>O Internet Explorer 3.0 foi o primeiro navegador a implementar o CSS 1.</li>
<li>O Internet Explorer 4.0 foi quem expôs o DOM e criou o DHTML, Dynamic HTML, com Dynamic Styling e manipulação de elementos do DOM via Javascript</li>
<li>O Internet Explorer 5.0 foi quem trouxe a semente do Ajax com o componente proprietário XMLHTTP e a técnica de buscar conteúdo de forma assíncrona com iframes usada no Outlook Web Access, dando origem ao XMLHttpRequest</li>
</ul>
<p>O período negro pós-2000 da Microsoft culminou no fracasso do Windows Vista e consequente sobrevida à la The Walking Dead tanto do Windows XP quanto do IE 6. O Google pegou o bastão de onde a Microsoft largou. O Core Business do Google é o AdWords. Controlar as plataformas Web faz parte da estratégia e o navegador continua sendo importante, porque ele é o novo sistema operacional desta geração, seja desktop ou mobile. Esqueçam Windows vs OS X vs GNU/Linux.</p>
<p>Outra tecnologia Microsoft que foi esquecida foi a primeira verdadeira tentativa de componentizar a Web. Foi com <a href="http://www.w3.org/TR/NOTE-HTMLComponents"target="_blank" rel="noopener">HTML Components</a> do fim de 1998. Nessa época ainda estávamos tentando dominar drag-and-drop de elementos com DHTML e começando a brincar com as possibilidades de fazer requisições sem precisar recarregar a página.</p>
<p>Se olhar esta página de <a href="http://bit.ly/1jV3VpJ"target="_blank" rel="noopener">APIs Legadas</a> da Microsoft teremos:</p>
<ul>
<li>Legacy DOM - substituída, claro, pelo DOM mais moderno</li>
<li>IE Architecture/Active X - substituído por Silverlight, Flash, ainda Applets e widgets</li>
<li>Browser Extensions - ainda temos extensions mas nenhum padrão</li>
<li>DHTML Behaviors e DHTML Data Binding - são os novos Web Components</li>
<li>Filters and Transitions - que até hoje temos browser-specific com modificadores como -moz ou -webkit, CSS 3</li>
<li>Vector Markup Language (VML) - que é o atual SVG e HTML 5 Canvas</li>
</ul>
<p>Não estou querendo dizer que é tudo mérito da Microsoft. A intenção é apenas explicar - para quem começou a usar a Internet na era pós-Browser Wars e pós-<a href="http://en.wikipedia.org/wiki/Dot-com_bubble"target="_blank" rel="noopener">Crash das Dot-coms</a> que Web Components não surgiu do nada, ela é o próximo passo lógico da evolução dos browsers e arquitetura Web.</p>
<p>Para o próprio Google essa não é a primeira vez. A primeira vez foi com <a href="http://en.wikipedia.org/wiki/Google_Web_Toolkit"target="_blank" rel="noopener">GWT</a> (Google Web Toolkit), que nasceu em 2006. Esse foi o auge dos frameworks MVC back-end que facilitavam desenvolvimento de aplicações Web usando Ajax. Foi a era pós-J2EE onde surgiram Rails, Django e outros Rails/Django-like como CakePHP, Grails.</p>
<p>Depois veio a era back-end assíncrono, desenterrando o problema <a href="http://www.kegel.com/c10k.html"target="_blank" rel="noopener">C10K de 1999 de Dan Kegel</a>, use case de Web Sockets (chat, APIs) que começa com Scala, vai para Node.js, chega em GoLang, Elixir. Novamente, não veio do nada, foi o próximo passo evolutivo depois do próprio Erlang, Twisted, Tornado, Java NIO/Netty, etc.</p>
<p>Em paralelo o Google investiu no Chrome e para isso precisou investir em Javascript e tornar o V8 algo realmente usável. Milhares de homens-hora e milhões de dólares depois, temos uma engine realmente performática. Com isso o mundo Javascript cresceu exponencialmente. Uma tecnologia que já se imaginava dominada na década de 90 ganhou nova roupagem e uma segunda chance. Com isso a arquitetura MVC2 evoluiu para um híbrido, com frameworks MVVM no client-side.</p>
<p>Portanto é natural evoluir do mindset GWT para Angular.js e eventualmente para Web Components/Polymer. E é onde estamos agora. Note que estamos sempre circulando os conceitos de MVC-clássico, MVC Model-2, MVP e agora MVVM, todos patterns originados do lendário MVC de <a href="http://heim.ifi.uio.no/~trygver/themes/mvc/mvc-index.html"target="_blank" rel="noopener">Trygve Reenskaug</a> do Smalltalk-80. Lembrando que já experimentamos programação orientada a componentes antes: foi a década de 90, que imaginou uma revolução ao usar componentes reusáveis para tornar o processo de programação meramente um jogo de arrastar blocos. Isso ficou conhecido como <a href="http://en.wikipedia.org/wiki/Rapid_application_development"target="_blank" rel="noopener">RAD</a> (Rapid Application Development), que deu origem a toolkits e IDEs. Foi o conceito que deu origem a PowerBuilder, Visual Basic, Delphi, Swing. E a evolução na direção de ferramentas CASE (Computer Aided Software Engineering). Achávamos que poderíamos criar diagramas (UML) e as ferramentas gerariam o código-fonte para nós. Já ouviu falar disso? Pois é, não deu muito certo. O Visual Studio de hoje <a href="http://msdn.microsoft.com/en-us/library/ff657795.aspx"target="_blank" rel="noopener">ainda tem suporte</a> a isso (yikes).</p>
<p>A diferença são as ondas da moda: na década de 90 ainda estávamos definindo o que era possível e, pelo bem ou pelo mal, o que a Microsoft inventou realmente foi revolucionário porque não havia precedente. A era pré-Bolha trouxe o MVC2 para o desenvolvimento Web com Web Objects e J2EE. O advento do Ajax e aplicações dinâmicas trouxe de GWT a Rails. E agora a era da Renascença do Javascript trouxe o MVVM e Web Components.</p>
<p>Não sei o que vem depois daqui, mas depois de organizar a informação histórica espero que mais alguém tenha insights do que vem pela frente. O que acham que Web Components realmente pode trazer que já não vimos com GWT e anteriores?</p>
]]></content:encoded><category>frontend</category><category>javascript</category><category>desenvolvimento-web</category></item><item><title>[#IF] ActiveAdmin + Editor HTML 5</title><link>https://akitaonrails.github.io/2013/05/13/if-activeadmin-editor-html-5/</link><guid isPermaLink="true">https://akitaonrails.github.io/2013/05/13/if-activeadmin-editor-html-5/</guid><pubDate>Mon, 13 May 2013 14:24:00 -0300</pubDate><description>&lt;p&gt;Uma das gems que eu mais uso em projetos é o &lt;a href="http://activeadmin.info/documentation.html"target="_blank" rel="noopener"&gt;ActiveAdmin&lt;/a&gt;, de todas as opções de admin para Rails que surgiram até hoje, esta foi a que melhor se adaptou na maioria dos projetos. Longe de ser perfeita, mas o suficiente para atender bem as necessidades de uma simples coleção de CRUDs.&lt;/p&gt;
&lt;p&gt;Outra vantagem é que pouco a pouco um pequeno ecossistema surgiu em torno do framework, adicionando funcionalidades como granularidade de permissões com &lt;a href="https://github.com/11factory/activeadmin-cancan"target="_blank" rel="noopener"&gt;CanCan&lt;/a&gt;, e eu já bloguei sobre sua excelente integração com o &lt;a href="http://www.akitaonrails.com/2012/07/23/activeadmin-best-in-place"target="_blank" rel="noopener"&gt;Best in Place&lt;/a&gt;. Desta vez experimentei outra extensão que gostei muito, o &lt;a href="https://github.com/stefanoverna/activeadmin-wysihtml5"target="_blank" rel="noopener"&gt;ActiveAdmin-WYSIHTML5&lt;/a&gt;.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Uma das gems que eu mais uso em projetos é o <a href="http://activeadmin.info/documentation.html"target="_blank" rel="noopener">ActiveAdmin</a>, de todas as opções de admin para Rails que surgiram até hoje, esta foi a que melhor se adaptou na maioria dos projetos. Longe de ser perfeita, mas o suficiente para atender bem as necessidades de uma simples coleção de CRUDs.</p>
<p>Outra vantagem é que pouco a pouco um pequeno ecossistema surgiu em torno do framework, adicionando funcionalidades como granularidade de permissões com <a href="https://github.com/11factory/activeadmin-cancan"target="_blank" rel="noopener">CanCan</a>, e eu já bloguei sobre sua excelente integração com o <a href="http://www.akitaonrails.com/2012/07/23/activeadmin-best-in-place"target="_blank" rel="noopener">Best in Place</a>. Desta vez experimentei outra extensão que gostei muito, o <a href="https://github.com/stefanoverna/activeadmin-wysihtml5"target="_blank" rel="noopener">ActiveAdmin-WYSIHTML5</a>.</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/362/image_screenshot.png" alt="ActiveAdmin-Dragonfly"  loading="lazy" /></p>
<p>Essa extensão foi criada pelo italiano <a href="https://github.com/stefanoverna"target="_blank" rel="noopener">Stefano Verna</a> que também fez diversas outras extensões, incluindo um com o <a href="https://github.com/stefanoverna/activeadmin-globalize3"target="_blank" rel="noopener">Globalize 3</a>, e um para fazer upload de imagens que utiliza uma outra gem chamada <a href="https://github.com/markevans/dragonfly"target="_blank" rel="noopener">Dragonfly</a>.</p>
<p>Um parêntese, o Dragonfly é um concorrente de <a href="http://www.akitaonrails.com/2010/06/23/akita-responde-upload-de-arquivos#.UZEeJys6WDQ"target="_blank" rel="noopener">Carrierwave e Paperclip</a>. Uma vantagem em relação aos anteriores é que ele herda somente de ActiveModel em vez de ActiveRecord, o que o torna naturalmente compatível com MongoMapper para usar com MongoDB.</p>
<p>Se somar com o Best In Place, CanCan, um versionador como o <a href="https://github.com/airblade/paper_trail"target="_blank" rel="noopener">PaperTrail</a>, já é possível fazer um CMS (Content Management System) bastante capaz rapidamente e, melhor, com boa qualidade, bons testes, facilidade de adicionar novas funcionalidades.</p>
<p>Num novo projeto podemos criar um model <tt>Page</tt> que terá o HTML:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>rails g model Page title body:text</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 adicionamos o que precisamos no <tt>Gemfile</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">gem</span> <span class="s1">&#39;activeadmin&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;activeadmin-dragonfly&#39;</span><span class="p">,</span> <span class="ss">github</span><span class="p">:</span> <span class="s1">&#39;stefanoverna/activeadmin-dragonfly&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;activeadmin-wysihtml5&#39;</span><span class="p">,</span> <span class="ss">github</span><span class="p">:</span> <span class="s1">&#39;stefanoverna/activeadmin-wysihtml5&#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>Execute o comando <tt>bundle</tt> para instalar. Agora precisamos criar os arquivos de configuração básicos:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>rails generate active_admin:install
rake activeadmin_wysihtml5:install:migrations </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>Finalmente, basta criar a configuração normal para o <tt>ActiveAdmin</tt> para expor o model <tt>Page</tt> criando o arquivo <tt>app/admin/page.rb</tt> 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="no">ActiveAdmin</span><span class="o">.</span><span class="n">register</span> <span class="no">Page</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">form</span> <span class="k">do</span> <span class="o">|</span><span class="n">f</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">    <span class="n">f</span><span class="o">.</span><span class="n">inputs</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="n">f</span><span class="o">.</span><span class="n">input</span> <span class="ss">:title</span>
</span></span><span class="line"><span class="cl">      <span class="n">f</span><span class="o">.</span><span class="n">input</span> <span class="ss">:body</span><span class="p">,</span> <span class="ss">as</span><span class="p">:</span> <span class="ss">:wysihtml5</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">f</span><span class="o">.</span><span class="n">buttons</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 opção <tt>:wysihtml5</tt> pode ser customizada para definir que tipos de controle o editor vai possuir. Com o Dragonfly ele automaticamente já ganha o modal para galeria de imagem com suporte a uploads (em outro artigo mostro como migrar o Carrierwave para Dragonfly e como configurar para fazer uploads para S3. Dica: leia a documentação e o código fonte do Dragonfly).</p>
<p>Não esqueça de adicionar os assets do ActiveAdmin no seu <tt>config/environments/production.rb</tt> para que a tarefa de pré-compilação do <a href="http://www.akitaonrails.com/2012/07/01/asset-pipeline-para-iniciantes"target="_blank" rel="noopener">Asset Pipeline</a> compile corretamente (muita gente esquece de fazer isso no começ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">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(active_admin.js active_admin.css active_admin/print.css)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/361/screenshot.png" alt="ActiveAdmin-WYSIHTML5"  loading="lazy" /></p>
<p>Por causa de opções como essas é que provavelmente não temos um CMS &ldquo;canônico&rdquo; no mundo Rails. É mais fácil (e muito mais prático e limpo) incorporar funcionalidades de CMS à sua aplicação do que tentar &ldquo;estuprar&rdquo; um CMS limitado para fazer mais do que sua função original.</p>
<p>Em particular, a categoria <a href="https://www.ruby-toolbox.com/categories/rails_admin_interfaces"target="_blank" rel="noopener">Rails Admin Interfaces</a> já evoluiu muito nos últimos anos. Uma recomendação: nunca vai existir um Admin que satisfaça tudo e todos. Se tentar fazer algo customizável e dinâmico demais o projeto pode facilmente sair do controle. Já passamos por isso com o ActiveScaffold e mesmo com o atual Rails Admin. O ActiveAdmin é mais &ldquo;quadrado&rdquo; nesse sentido mas é mais simples e explícito no que faz. Alguns não gostam porque para customizá-lo você precisa usar <a href="https://github.com/justinfrench/formtastic"target="_blank" rel="noopener">Formtastic</a>. Existem facções que gostam, outras que preferem SimpleForms, e outras que preferem não usar nenhum deles, mas novamente é uma questão de preferência já que ambos tem prós e contras.</p>
<p>O objetivo de mostrar esta gem é demonstrar como o ActiveAdmin pode ser estendido para funcionalidades interessantes sem tanto trabalho e/ou necessidade de criar &ldquo;yet-another-clone&rdquo; do ActiveAdmin.</p>
]]></content:encoded><category>rails</category><category>tutoriais</category><category>desenvolvimento-web</category></item><item><title>Você já está usando ETAGs, certo?</title><link>https://akitaonrails.github.io/2010/05/25/voce-ja-esta-usando-etags-certo/</link><guid isPermaLink="true">https://akitaonrails.github.io/2010/05/25/voce-ja-esta-usando-etags-certo/</guid><pubDate>Tue, 25 May 2010 10:15:00 -0300</pubDate><description>&lt;p&gt;&lt;strong&gt;Atualizado 26/05:&lt;/strong&gt; Ajustei o artigo de acordo com os comentários do Tapajós :-)&lt;/p&gt;
&lt;p&gt;Essa dica é meio velha, mas como muita gente ainda desconhece vamos falar dela. Um recurso que surgiu no &lt;a href="http://guides.rubyonrails.org/2_2_release_notes.html#better-integration-with-http--out-of-the-box-etag-support"target="_blank" rel="noopener"&gt;Rails 2.2&lt;/a&gt; é o suporte a ETAG. Se você ainda não usa, deveria. Isso porque é super simples, vai melhorar a performance do seu site para seus usuário e sai praticamente de graça, sem efeitos colaterais.&lt;/p&gt;
&lt;p&gt;Para entender, vamos ver a estrutura de uma action comum de ActionController:&lt;/p&gt;</description><content:encoded><![CDATA[<p><strong>Atualizado 26/05:</strong> Ajustei o artigo de acordo com os comentários do Tapajós :-)</p>
<p>Essa dica é meio velha, mas como muita gente ainda desconhece vamos falar dela. Um recurso que surgiu no <a href="http://guides.rubyonrails.org/2_2_release_notes.html#better-integration-with-http--out-of-the-box-etag-support"target="_blank" rel="noopener">Rails 2.2</a> é o suporte a ETAG. Se você ainda não usa, deveria. Isso porque é super simples, vai melhorar a performance do seu site para seus usuário e sai praticamente de graça, sem efeitos colaterais.</p>
<p>Para entender, vamos ver a estrutura de uma action comum de ActionController:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">index</span>  
</span></span><span class="line"><span class="cl"> <span class="vi">@posts</span> <span class="o">=</span> <span class="no">Post</span><span class="o">.</span><span class="n">all</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="c1"># index.html.erb  </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>Toda vez que você requisitar esta action, o Rails vai buscar os dados no banco, renderizar o template ERB e enviar tudo pro cliente, todas as vezes. Você pode colocar uma regra de expires para que o browser do usuário não peça a mesma página por X tempos, mas isso não é eficiente se você efetivamente quer que o conteúdo novo seja enviado assim que for atualizado. Agora veja esta outra forma:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">index</span>  
</span></span><span class="line"><span class="cl"><span class="sb">`posts = Post.all
</span></span></span><span class="line"><span class="cl"><span class="sb">  if stale?(:etag =&gt; etag(`</span><span class="n">posts</span><span class="p">),</span> <span class="ss">:public</span> <span class="o">=&gt;</span> <span class="kp">true</span><span class="p">)</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="c1"># index.html.erb  </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></span><span class="line"><span class="cl"><span class="kp">private</span>  
</span></span><span class="line"><span class="cl"> <span class="k">def</span> <span class="nf">etag</span><span class="p">(</span><span class="n">collection</span><span class="p">)</span>  
</span></span><span class="line"><span class="cl"> <span class="n">collection</span><span class="o">.</span><span class="n">inject</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">etag</span><span class="p">,</span> <span class="n">item</span><span class="o">|</span> <span class="n">etag</span> <span class="o">+=</span> <span class="n">item</span><span class="o">.</span><span class="n">updated_at</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="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ó isso. Acontece o seguinte, um ETAG é um identificador único de um determinado recurso. Pense como um Hash. No caso você pode passar qualquer número ao método “stale?”. Como eu sei que toda vez que um Post é atualizado o campo “updated_at” também muda, significa que basta um Post mudar para que a soma dos “updated_at” gerado no método “etag” que eu criei vai mudar. Agora, para que serve esse número?</p>
<p>Quando o Rails terminar de renderizar o template ERB, ele vai adicionar o seguinte cabeçalho na resposta ao navegador do usuário:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>ETAG: e2b62c2507dd32e23af8e89f305cd864  </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>
<ul>
<li></li>
</ul>
<p>Isso juntamente com o código de status “200 OK”. Porém, se o usuário pedir a mesma página outra vez, o navegador vai enviar junto com a requisição o seguinte cabeçalho:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>HTTP_IF_NONE_MATCH: e2b62c2507dd32e23af8e89f305cd864  </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 seja, ele vai dizer ao servidor: <em>“não me mande nada se o ETAG do recurso que estou pedindo ainda for o mesmo”</em>. Daí quando isso chegar no Rails, ele vai buscar os posts no banco e quando cair no método “stale?” ele vai notar que o navegador enviou um ETAG e vai comparar com o ETAG que ele acabou de computar. Se nenhum post mudou os ETAGs serão iguais. Nesse caso o Rails não vai processar o ERB e em vez disso vai enviar uma resposta vazia apenas com o código de status “304 Not Modified”, que o navegador saberá interpretar como <em>“beleza, nada mudou, então mantém a mesma página que já tenho”</em></p>
<p>Existe uma variação que é 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">show</span>  
</span></span><span class="line"><span class="cl"> <span class="vi">@post</span> <span class="o">=</span> <span class="no">Post</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="n">fresh_when</span><span class="p">(</span><span class="ss">:etag</span> <span class="o">=&gt;</span> <span class="vi">@post</span><span class="o">.</span><span class="n">updated_at</span><span class="o">.</span><span class="n">to_i</span><span class="p">,</span> <span class="ss">:public</span> <span class="o">=&gt;</span> <span class="kp">true</span><span class="p">)</span>  
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<ul>
<li></li>
</ul>
<p>O método <a href="http://apidock.com/rails/ActionController/Base/fresh_when"target="_blank" rel="noopener">fresh_when</a> é chamado por baixo pelo “stale?”. Você usar o fresh_when quando não precisa customizar nada como o “render”. Já com o “stale?”, se vier o “If-None-Match” no cabeçalho HTTP, ele não vai executar nada que estiver dentro do bloco “if stale?”, daí vem os ganhos de processamento, não só da renderização do template mas de quaisquer outros processamentos. 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="k">def</span> <span class="nf">show</span>  
</span></span><span class="line"><span class="cl"> <span class="vi">@post</span> <span class="o">=</span> <span class="no">Post</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="k">if</span> <span class="n">stale?</span><span class="p">(</span><span class="ss">:etag</span> <span class="o">=&gt;</span> <span class="vi">@post</span><span class="o">.</span><span class="n">updated_at</span><span class="o">.</span><span class="n">to_i</span><span class="p">,</span> <span class="ss">:public</span> <span class="o">=&gt;</span> <span class="kp">true</span><span class="p">)</span>  
</span></span><span class="line"><span class="cl"> <span class="vi">@comments</span> <span class="o">=</span> <span class="no">Post</span><span class="o">.</span><span class="n">comments</span><span class="o">.</span><span class="n">all</span><span class="p">(</span><span class="ss">:order</span> <span class="o">=&gt;</span> <span class="s2">&#34;updated_at DESC&#34;</span><span class="p">,</span> <span class="ss">:conditions</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="ss">:spam</span> <span class="o">=&gt;</span> <span class="kp">false</span> <span class="p">})</span>  
</span></span><span class="line"><span class="cl"> <span class="vi">@tags</span> <span class="o">=</span> <span class="no">Post</span><span class="o">.</span><span class="n">tags</span><span class="o">.</span><span class="n">all</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></span><span class="line"><span class="cl"> <span class="nb">format</span><span class="o">.</span><span class="n">xml</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>Neste exemplo, se vier o ETAG no “If-None-Match”, vamos economizar buscar os comentários e os tags do Post. Além de evitar gerar o mesmo HTML de novo também evita mais queries no banco de dados.</p>
<p>Outra coisa é que se você usar o método “fresh_when” precisa tomar cuidado para não cair em exceção de “Double Render Error”. Numa action Rails você não pode ter duas chamadas a “render” ou chamar “render” e “redirect_to” junto, por motivos óbvios. Por isso, esse código abaixo dará problemas:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">show</span>  
</span></span><span class="line"><span class="cl"> <span class="vi">@post</span> <span class="o">=</span> <span class="no">Post</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="n">fresh_when</span><span class="p">(</span><span class="ss">:etag</span> <span class="o">=&gt;</span> <span class="vi">@post</span><span class="o">.</span><span class="n">updated_at</span><span class="o">.</span><span class="n">to_i</span><span class="p">,</span> <span class="ss">:public</span> <span class="o">=&gt;</span> <span class="kp">true</span><span class="p">)</span>  
</span></span><span class="line"><span class="cl"> <span class="n">render</span> <span class="ss">:action</span> <span class="o">=&gt;</span> <span class="s2">&#34;post&#34;</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>É necessário fazer uma checagem antes de chamar o “render” do exemplo acima:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<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">show</span>  
</span></span><span class="line"><span class="cl"> <span class="vi">@post</span> <span class="o">=</span> <span class="no">Post</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="n">fresh_when</span><span class="p">(</span><span class="ss">:etag</span> <span class="o">=&gt;</span> <span class="vi">@post</span><span class="o">.</span><span class="n">updated_at</span><span class="o">.</span><span class="n">to_i</span><span class="p">,</span> <span class="ss">:public</span> <span class="o">=&gt;</span> <span class="kp">true</span><span class="p">)</span>  
</span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="n">response</span><span class="o">.</span><span class="n">status</span> <span class="o">==</span> <span class="mi">200</span>  
</span></span><span class="line"><span class="cl"> <span class="n">render</span> <span class="ss">:action</span> <span class="o">=&gt;</span> <span class="s2">&#34;post&#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>O “if” acima resolve porque se o “fresh_when” detectou um ETAG ele já configurou o “response.status” para ser igual a “304” que é o código de “Not Modified” no cabeçalho de resposta.</p>
<p>E qual é o ganho? Nesse exemplo – ultra-simples – em modo desenvolvimento, no meu notebook, uma requisição normal com meros 5 registros devolveu em 9ms. Com a adição do ETAG isso caiu para 5ms, um ganho de cerca e <strong>40%</strong> de performance. Claro isso vai variar bastante, mas é claro que o tempo gasto em não processar o template ERB e em não enviar algumas dezenas de kilobytes do HTML gerado é uma boa economia tanto de processamento quanto de banda.</p>
<p>Um fator a se lembrar é que neste caso o banco de dados ainda é acessado, para poder computar o ETAG. E dependendo do método que computa esse valor, o processamento do ETAG em si pode ser custoso. Obviamente você não vai devolver listas com centenas de registros numa homepage, portanto valem as mesmas boas práticas.</p>
<p>Como exemplo temos este blog mesmo. Antigamente eu utilizava <a href="/2008/08/21/tutorial-de-rails-caching-parte-1">Page Caching</a> no meu blog, ou seja, gerava o HTML estático para economizar processamento. Mas o controle disso pode ser complicado dependendo do seu site. O <a href="http://twitter.com/tapajos"target="_blank" rel="noopener">Marcos Tapajós</a> sugeriu usar ETAG e ele tinha razão: meu servidor não ficou muito mais pesado do que antes e isso economizou muito código extra pra lidar com expiração dos meus caches estáticos.</p>
<h2>Clientes de Web Services<span class="hx:absolute hx:-mt-20" id="clientes-de-web-services"></span>
    <a href="#clientes-de-web-services" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Agora um conceito que é igualmente importante: clientes de web services deveriam se comportar da mesma forma na presença de cabeçalhos como “If-Modified-Since”, “If-None-Match”, “Last Modified” e assim por diante. Ou seja, seu servidor, ao enviar o XML, JSON, Atom, etc deve enviar de volta informações como ETAG, Cache-Control e os clientes que consomem essa informação deveriam ser educados (como o <a href="http://twitter.com/guilhermesilveira"target="_blank" rel="noopener">Guilherme Silveira</a>) e respeitá-las. Ou seja, devolver o “If-None-Match” na presença de ETAG e usar seu cache local caso o servidor tenha enviado informações de expiração.</p>
<p>Resolvi testar o próprio ActiveResource do Rails, que dá uma forma simples de criar clientes de web services <em>REST-like</em> como o que o Rails implementa. Porém parece que ele não respeita essas informações. Não tenho 100% de certeza ainda, mas no meu teste eu envio de volta um ETAG e o cliente ActiveResource não envia o “If-None-Match”, forçando meu servidor a reprocessar a informação.</p>
<p>Se você lida com clientes de web services, lembre-se de implementar essas e outras regras que o HTTP especifica e torne-se um bom cidadão da internet.</p>
]]></content:encoded><category>rails</category><category>performance</category><category>desenvolvimento-web</category></item><item><title>Conversando com Scott Hanselman</title><link>https://akitaonrails.github.io/2008/02/18/conversando-com-scott-hanselman/</link><guid isPermaLink="true">https://akitaonrails.github.io/2008/02/18/conversando-com-scott-hanselman/</guid><pubDate>Mon, 18 Feb 2008 09:45:00 -0300</pubDate><description>&lt;p&gt;Esta semana entrevistei mais uma pessoa do campo Microsoft, &lt;a href="http://www.hanselman.com/"target="_blank" rel="noopener"&gt;Scott Hanselman&lt;/a&gt;. Eu o conheço pelo seu podcast &lt;a href="http://www.hanselminutes.com/"target="_blank" rel="noopener"&gt;Hanselminutes&lt;/a&gt;. Em um episódio ele &lt;a href="https://akitaonrails.github.io/2007/07/08/hanselminutes-entrevista-com-david-hansson-e-martin-fowler/"&gt;entrevistou&lt;/a&gt; tanto Martin Fowler quanto David Hansson na RailsConf do ano passado — uma conversa realmente notável.&lt;/p&gt;
&lt;p&gt;&lt;img src="http://s3.amazonaws.com/akitaonrails/assets/2008/2/18/1370206727_84d760de06.jpg" alt="" loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;Ele também postou um excelente &lt;a href="http://www.hanselman.com/blog/ASPNET35ExtensionsPlusMVCHowToScreencast.aspx"target="_blank" rel="noopener"&gt;screencast&lt;/a&gt; sobre o novo framework MVC alternativo da Microsoft e achei que seria ótimo tê-lo aqui no blog para falar sobre tecnologia e frameworks web. Como já disse antes, acho que não devemos ficar alheios ao que está acontecendo em outras frentes, e Scott é uma pessoa de pensamento avançado e mente aberta.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Esta semana entrevistei mais uma pessoa do campo Microsoft, <a href="http://www.hanselman.com/"target="_blank" rel="noopener">Scott Hanselman</a>. Eu o conheço pelo seu podcast <a href="http://www.hanselminutes.com/"target="_blank" rel="noopener">Hanselminutes</a>. Em um episódio ele <a href="/2007/07/08/hanselminutes-entrevista-com-david-hansson-e-martin-fowler/">entrevistou</a> tanto Martin Fowler quanto David Hansson na RailsConf do ano passado — uma conversa realmente notável.</p>
<p><img src="http://s3.amazonaws.com/akitaonrails/assets/2008/2/18/1370206727_84d760de06.jpg" alt=""  loading="lazy" /></p>
<p>Ele também postou um excelente <a href="http://www.hanselman.com/blog/ASPNET35ExtensionsPlusMVCHowToScreencast.aspx"target="_blank" rel="noopener">screencast</a> sobre o novo framework MVC alternativo da Microsoft e achei que seria ótimo tê-lo aqui no blog para falar sobre tecnologia e frameworks web. Como já disse antes, acho que não devemos ficar alheios ao que está acontecendo em outras frentes, e Scott é uma pessoa de pensamento avançado e mente aberta.</p>
<p><strong>AkitaOnRails:</strong> Antes de tudo, deixa eu te parabenizar sr. <a href="http://www.hanselman.com/blog/BlueBadge.aspx"target="_blank" rel="noopener">progman.exe</a> :-) Entendo que isso foi algo pelo qual você estava muito animado. Pode nos contar um pouco mais sobre sua transição para a Microsoft e suas novas responsabilidades?</p>
<p><strong>Scott Hanselman:</strong> Estou trabalhando para Simon Muzio, que cuida de <a href="http://www.asp.net"target="_blank" rel="noopener">www.asp.net</a>, <a href="http://www.iis.net"target="_blank" rel="noopener">www.iis.net</a> e grandes sites cheios de conteúdo como esses. Gosto de pensar que meu trabalho é escrever um livro que nunca vai terminar, onde esse livro são posts de blog, artigos, screencasts, vídeos, palestras, etc. No fundo sou um professor, então mesmo tendo o título de &ldquo;Community Liaison&rdquo;, prefiro &ldquo;Professor&rdquo;.</p>
<p><strong>AkitaOnRails:</strong> Conta pra gente sobre sua história. Como você começou no mundo da computação? Você sempre foi mais voltado para Windows ou tinha uma base no mundo Unix também? E antes de sua recente saída para a Microsoft você trabalhava na <a href="http://www.hanselman.com/blog/CheckFreeClosesDealToAcquireCorillianNowTheRealWorkBegins.aspx"target="_blank" rel="noopener">Corillian</a>. Qual era sua função lá?</p>
<p><img src="http://s3.amazonaws.com/akitaonrails/assets/2008/2/18/computerolympics.jpg" alt=""  loading="lazy" /></p>
<p><strong>Scott Hanselman:</strong> <a href="http://www.hanselman.com/blog/TeachingChildrenAndKidsToProgramTheOldSchoolWay.aspx"target="_blank" rel="noopener">Este post de blog</a> fala sobre meus primeiros anos na escola.</p>
<blockquote>
  <p>Viu aquele livro à direita? Foi assim que aprendi a programar. Esse livro era O livro em 1984. Digitei o livro inteiro num Commodore 64.</p>

</blockquote>
<p>E uma professora MUITO <a href="http://www.hanselman.com/blog/MrsMayfieldHillMyFifthGradeTeacher.aspx"target="_blank" rel="noopener">influente</a> que me colocou no caminho certo.</p>
<blockquote>
  <p>Marianne Mayfield é minha professora da quinta série e a razão pela qual estou em computação hoje. Ela esteve no meu casamento, em formaturas, em eventos de família. Vinte anos depois ainda somos próximos. Quando eu era jovem, ela reconheceu que eu estava &ldquo;em risco de dar problema&rdquo; e se sentou com minha família para encontrar uma forma de me colocar de volta nos trilhos. Ela viu que eu estava fascinado pelo Apple II na nossa sala de aula e arranjou para que a gente pudesse levar o computador para fora da escola, desde que estivesse de volta antes do domingo à noite sem que ninguém notasse.</p>

</blockquote>
<p>(clique nos links acima para mais detalhes)</p>
<p><strong>Scott Hanselman:</strong> Eu era o Arquiteto-Chefe na Corillian, trabalhando com outros engenheiros e arquitetos em toda a empresa, planejando, projetando e codando a próxima geração da Plataforma de Internet Banking da Corillian.</p>
<p><strong>AkitaOnRails:</strong> Aplaudo sua campanha <a href="http://www.hanselman.com/blog/TeamHanselmanAndDiabetesWalk2007.aspx"target="_blank" rel="noopener">Team Hanselman</a> para ajudar no combate à Diabetes. Entendo que você também tem Diabetes e que, felizmente, conseguiu controlá-la hoje em dia. Pode nos contar essa história e os objetivos da sua campanha?</p>
<p><strong>Scott Hanselman:</strong> Minha história pessoal está <a href="http://hanselman.com/fightdiabetes"target="_blank" rel="noopener">aqui</a>:</p>
<blockquote>
  <p>Atualmente tomo três injeções de Symlin por dia enquanto uso uma bomba de insulina 24 horas por dia, inclusive enquanto durmo. A bomba me poupa de mais seis injeções por dia, que tomei durante 8 anos antes da bomba. Meço minha glicose picando meu dedo entre 8 e 10 vezes por dia — são cerca de 46.500 picadas até agora, e ainda há um longo caminho pela frente.</p>

</blockquote>
<p>E tem uma boa explicação da diabetes <a href="http://www.hanselman.com/blog/ScottsDiabetesExplanationTheAirplaneAnalogy.aspx"target="_blank" rel="noopener">aqui</a>:</p>
<blockquote>
  <p>É aqui que a analogia fica interessante. Lembre que na analogia estamos voando de Los Angeles para Nova York, mas só podemos verificar nossa altitude sete vezes. E só podemos mudar de altitude (tomar insulina) menos de dez vezes. Mas quando verifico minha glicose, estou vendo o passado. Estou vendo uma leitura de como estava minha glicose há 15 minutos. E quando tomo insulina, ela só começa a reduzir minha glicose depois de pelo menos 30 minutos.</p>

</blockquote>
<p>A campanha acabou arrecadando mais de USD$30.000, e esse dinheiro será aplicado em pesquisa, educação e programas relacionados à diabetes.</p>
<p><strong>AkitaOnRails:</strong> <em>&ldquo;O que diabos você estava fazendo numa RailsConf?&rdquo;</em> Isso é o que acho que pessoas de fora do mundo de blogs Microsoft perguntariam a você :-) Então, você é apenas curioso sobre Rails, ou chega a desenvolver aplicações baseadas em Rails? Estou muito interessado em conhecer sua opinião sobre Rails. Como você o encontrou? O que você acha de melhor no Rails? E mais importante, o que você acha que Rails poderia melhorar?</p>
<p><strong>Scott Hanselman:</strong> Estou sempre experimentando diferentes linguagens de programação e frameworks. Tenho feito Rails de vez em quando há cerca de 18 meses, mas ainda não clicou pra mim. Tenho certeza que vai em breve. O padrão MVC é muito antigo… eu fazia isso em Java há mais de 10 anos, e certamente o padrão remonta a 30 anos. Acho que Rails é um bom framework considerando sua pouca idade. Mas acho que é o Ruby que tem a magia de verdade, e vejo que a estética do Ruby está muito em linha com a minha.</p>
<p><strong>AkitaOnRails:</strong> Então, seguindo minhas preferências tendenciosas, o próximo artigo de que gosto é seu <a href="http://www.hanselman.com/blog/ASPNET35ExtensionsPlusMVCHowToScreencast.aspx"target="_blank" rel="noopener">screencast</a> sobre o framework CTP ASP.NET MVC. Vejo que esse novo framework se parece muito com Rails. Claro, pode parecer assim apenas porque ambos implementam MVC. Por outro lado, ele usa expressões muito similares ao Rails em todo lugar. Falta um pouco de &ldquo;mágica de convenção-sobre-configuração&rdquo;, mas pelo que vi parece muito melhor do que fazer ASP.NET convencional. O que você acha dele? Pode resumir um pouco para quem não conhece esse produto? E o que você acha que ainda vai melhorar nele?</p>
<p><strong>Scott Hanselman:</strong> Se alguém aponta para Rails e diz &ldquo;isso é MVC&rdquo; então tudo mais vai se parecer com Rails, claro. Qualquer bom framework MVC precisa não só seguir o padrão adequadamente, mas também abstrair o HTTP (assumindo que é um framework web) de forma limpa, mas não completa. A implementação do ASP.NET MVC ainda carrega alguma bagagem que sempre vai precisar carregar porque o ASP.NET WebForms veio primeiro e não foi projetado do zero com testabilidade em mente. Dito isso, tudo se resume a alternativas. Use o framework que te deixa feliz e em que você se sente mais produtivo. Alguma fração, talvez 20%, dos desenvolvedores ASP.NET vai preferir MVC. Agora eles têm uma opção com suporte da Microsoft e boa integração com C# 3.0.</p>
<p><img src="http://s3.amazonaws.com/akitaonrails/assets/2008/2/18/505403549_2ad6d13ce7.jpg" alt=""  loading="lazy" /><br>
na RailsConf'07</p>
<p><strong>AkitaOnRails:</strong> E o que seria o mundo tech sem discussões e flame wars? Entendo que a maioria dos desenvolvedores .NET nem presta atenção a Rails ou outros frameworks. Até ouvi que há alguns desenvolvedores questionando coisas como <em>&ldquo;Por que mais um framework web se o ASP.NET já existe?&rdquo;</em> Ou seja, por que reinventar a roda e dividir atenções? Particularmente acho que esse novo MVC é o caminho a seguir, mas é inegável que o velho ASP.NET tem muito mais aceitação e suporte. Como reconciliar os dois pontos de vista?</p>
<p><strong>Scott Hanselman:</strong> Isso é puro FUD (fear, uncertainty, and doubt — medo, incerteza e dúvida). Que pensem o que quiserem, esse framework é apenas sobre alternativas. WebForms é ótimo, e se você está fazendo aplicações Line of Business (LOB) com grids complexos e afins, ele não vai a lugar nenhum. No entanto, se você é uma equipe ágil que preza TDD e ficou frustrado com ViewState, então confira ASP.NET MVC. Não há nada a reconciliar. Algumas pessoas vão de carro, outras de moto, outras a pé. O que importa é chegar do ponto A ao ponto B e se sentir bem durante a jornada.</p>
<p><strong>AkitaOnRails:</strong> Falando em flame wars, há provavelmente um assunto que me dá arrepios: as teorias conspiratórias paranoicas sobre Miguel de Icaza e o <a href="http://www.hanselman.com/blog/MonoAndItsManyFacetsScreencast.aspx"target="_blank" rel="noopener">Mono</a>. Muita gente acredita de fato que Miguel está &ldquo;vendendo&rdquo; parte do mundo open source para a Microsoft. Você já usou? O que você ouve da comunidade e qual é sua opinião sobre o assunto?</p>
<p><strong>Scott Hanselman:</strong> Sim, já usei Mono e acho que é uma ótima forma de fazer sua aplicação .NET funcionar no Linux ou Mac. Acho Miguel brilhante e um excelente dançarino. Ouço na comunidade que as pessoas estão felizes por Mono existir e estar disponível como uma opção.</p>
<p><strong>AkitaOnRails:</strong> Provavelmente vou levar uma maldição por isso, mas não sou &ldquo;fortemente&rdquo; contra a Microsoft como alguns fundamentalistas são. É público e notório que a Microsoft é uma grande corporação e, como tal, cometeu sua parte de atos equivocados. Muitos deles já foram resolvidos na justiça. Então é mais complexo do que as pessoas geralmente pensam. Prefiro assumir que dentro da Microsoft há muitas pessoas muito inteligentes.</p>
<p><strong>Scott Hanselman:</strong> Fico fora de questões legais. A Microsoft é uma empresa enorme com muita gente inteligente, mas na prática é muitas empresas de porte médio trabalhando juntas (geralmente). Não posso falar sobre &ldquo;atos equivocados&rdquo;. Só posso dizer que faço meu melhor todo dia para não ser mau, assim como meus colegas de trabalho. Não há nenhum plano maléfico mestre no meu grupo.</p>
<p><strong>AkitaOnRails:</strong> Particularmente gosto do roadmap para Windows Server 2008 (com sua abordagem minimalista, finalmente tendo instalado apenas o que você precisa). No ano passado você estava experimentando o <a href="http://www.hanselman.com/blog/ScreencastHowToIIS7AndPHPWithFastCGI.aspx"target="_blank" rel="noopener">IIS 7</a> e seu novo suporte a FastCGI. Acho que era destinado principalmente ao suporte de PHP mas teoricamente poderia lidar com Ruby on Rails também. Você já tentou Rails lá? Acho que a Microsoft claramente está indo atrás do segmento de menor custo com Windows Server 2008 + IIS 7, contra as caixas baratas de Linux+Apache. Acho que não conseguirão chegar lá em termos de preço, mas o que você acha dele como competidor de serviços de internet?</p>
<p><strong>Scott Hanselman:</strong> Tenho pouco poder nessa arena, mas toda vez que alguém quiser ouvir estou defendendo um SKU barato de Servidor Web. Sou um grande fã do IIS7 e gostaria que o máximo de pessoas possível o experimentasse. Não testei IIS7+Rails mas ouvi rumores de pessoas conseguindo fazer funcionar.</p>
<p><strong>AkitaOnRails:</strong> Há muitos recursos novos no .NET 3.5. LINQ é o que mais me interessa. Pode nos dar uma breve descrição de por que é tão interessante? E o que você acha das melhores melhorias da versão 3.5? Você já está usando o Orcas? Qual é sua opinião? Ah, e você conhece o projeto <a href="http://www.sapphiresteel.com/"target="_blank" rel="noopener">Sapphire in Steel</a>?</p>
<p><strong>Scott Hanselman:</strong> LINQ é interessante porque é sua própria Linguagem de Domínio Específico para consultar objetos de todos os tipos, e está vivendo diretamente dentro de VB e C#. É um compilador MUITO mais inteligente e é um prazer trabalhar com ele. Instalei o Sapphire In Steel e conheço algumas pessoas que adoram. Estou animado para ver Rails no .NET.</p>
<p><strong>AkitaOnRails:</strong> Uma das vantagens da estratégia Microsoft .NET foi estar preparada desde o primeiro dia para ser agnóstica em relação a linguagens. Agora temos o DLR que foi derivado dos trabalhos no IronPython e não no IronRuby. Mas há ainda outra linguagem interessante que poucas pessoas conhecem: F#, a linguagem inspirada em OCaml. Você já experimentou?</p>
<p><strong>Scott Hanselman:</strong> Sim, e fiz dois podcasts sobre ela, um com <a href="http://www.hanselman.com/blog/HanselminutesPodcast76FWithRobertPickering.aspx"target="_blank" rel="noopener">Robert Pickering</a>, autor de um livro sobre F# e outro com <a href="http://www.hanselman.com/blog/HanselminutesPodcast96StartingSmallWithFWithDustinCampbell.aspx"target="_blank" rel="noopener">Dustin Campbell</a>, um entusiasta. Estou animadíssimo que ela vai ser uma linguagem de primeira classe dentro do VS.</p>
<p><img src="http://s3.amazonaws.com/akitaonrails/assets/2008/2/18/1930668335_0719ad4d3f.jpg" alt=""  loading="lazy" /><br>
com John Lam</p>
<p><strong>AkitaOnRails:</strong> Não sou contra comparações técnicas, precisamos fazer esse tipo de coisa para decidir nossos passos em projetos. Mas sou contra desafios sem sentido. Rails está longe de ser perfeito e sou o primeiro a dizer isso. Dito isso, é ótimo para um nicho específico de desenvolvimento web. Costumo recomendar que bons programadores devem expandir seus horizontes e aprender o máximo possível de coisas novas. Não dá para saber tudo perfeitamente, mas é sempre bom estar ciente das possibilidades. Você <a href="http://www.hanselman.com/blog/TheProgrammingLanguageExplosion.aspx"target="_blank" rel="noopener">escreveu algo</a> nessa linha. O que você acha disso?</p>
<p><strong>Scott Hanselman:</strong> Rails é bom no que faz dentro do seu nicho. Você não cria aplicativos desktop Windows com Rails e tudo bem. É por isso que não comparamos Rails com WPF, mas com frameworks web. Comparação é boa e sempre há algo bom para encontrar dos dois lados. Evito flame wars também. É inútil. Se o sapato aperta, não use. Mas experimente o máximo de sapatos que puder!</p>
<p><strong>AkitaOnRails:</strong> No ano passado você escreveu um <a href="http://www.hanselman.com/blog/TheSkynetComputeCloudIThinkThereIsAWorldMarketForMaybeFiveComputers.aspx"target="_blank" rel="noopener">post interessante</a> sobre o futuro. Google é esse grande gigante, Amazon lançou produtos surpreendentemente bons nos últimos meses como S3, EC2, SimpleDb. Não estou bem a par da estratégia atual do Microsoft Live. Pode dizer algo sobre para onde está rumando?</p>
<p><strong>Scott Hanselman:</strong> Não estou nesse grupo então não tenho nenhum conhecimento além do que já foi dito. No entanto, ainda mantenho minhas previsões naquele post!</p>
<blockquote>
  <p>A computação vai se mover para a Nuvem. Já está acontecendo, estamos 20% lá. A ideia existe desde o começo e vai, na minha opinião, continuar surgindo até que realmente aconteça e construamos a Skynet. Uma dessas nuvens de computação vai certamente acabar em órbita.</p>

</blockquote>
<p><strong>AkitaOnRails:</strong> E claro, de certa forma &ldquo;preciso&rdquo; te perguntar isso: você usa Windows Vista? Como tem sido para você considerando que há uma enorme campanha underground contra ele? Você acha que é tão ruim quanto todo mundo diz? Não posso comentar porque nunca o testei fora da minha máquina virtual (sou um cara de Mac), mas eles venderam 100 milhões de licenças.</p>
<p><strong>Scott Hanselman:</strong> Com certeza. Rodo Vista 64-bit em 3 máquinas e Vista 32-bit em 2 máquinas. Foi difícil nos primeiros 3 meses enquanto o suporte a drivers deixava a desejar, mas agora está ótimo. Tenho 2 máquinas com Vista que não foram reinicializadas há meses, apenas colocadas em modo de suspensão e acordadas. Depois que você consegue bons drivers de vídeo, geralmente tá tranquilo.</p>
<p>As pessoas implicam com o Vista, mas esquecem a vasta gama de hardware em que ele tem que rodar. O OS X só precisa rodar em Macs, e em uma faixa estreita deles. O Vista tem que rodar em milhões de combinações de hardware. Drivers de terceiros são DE LONGE o que mais trava o Vista, e ele leva a culpa. Por isso rodo 64-bit e uso apenas drivers assinados. Estou encantado com o 64-bit. Funciona muito bem.</p>
<p><strong>AkitaOnRails:</strong> Ok, é isso :-) Muito obrigado!</p>
<p><img src="http://s3.amazonaws.com/akitaonrails/assets/2008/2/18/90081476_85f9a226dd.jpg" alt=""  loading="lazy" /></p>
]]></content:encoded><category>entrevistas</category><category>microsoft</category><category>desenvolvimento-web</category></item><item><title>Conversando com Adrian Holovaty</title><link>https://akitaonrails.github.io/2008/01/01/conversando-com-adrian-holovaty/</link><guid isPermaLink="true">https://akitaonrails.github.io/2008/01/01/conversando-com-adrian-holovaty/</guid><pubDate>Tue, 01 Jan 2008 16:47:00 -0200</pubDate><description>&lt;p&gt;&lt;strong&gt;English readers, click &lt;a href="https://akitaonrails.github.io/2008/01/01/chatting-with-adrian-holovaty/"&gt;here&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Traducción en &lt;a href="http://www.marcelor.com/2008/01/conversando-con-adrian-holovaty-creador-del-proyecto-django-traduccion.html"target="_blank" rel="noopener"&gt;Español&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Como prometi depois da entrevista com &lt;a href="http://www.akitaonrails.com/2007/12/15/conversando-com-avi-bryant-parte-1"target="_blank" rel="noopener"&gt;Avi Bryant&lt;/a&gt; , aqui vai outra grande conversa com &lt;a href="http://www.holovaty.com/"target="_blank" rel="noopener"&gt;Adrian Holovaty&lt;/a&gt;, o conhecido criador do framework web &lt;a href="http://www.djangoproject.com/"target="_blank" rel="noopener"&gt;Django&lt;/a&gt; escrito em Python.&lt;/p&gt;
&lt;p&gt;&lt;img src="http://s3.amazonaws.com/akitaonrails/assets/2007/12/31/387373570_870fa6257c.jpg" alt="" loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;Para mim esta é uma matéria importante porque eu sempre disse que tecnologia não tem que ser sobre divórcio. Tecnologia é sobre integração. Sou um programador e evangelista Ruby on Rail tempo integral, mas acima de tudo, eu tento ser um ‘bom’ programador. E bons programadores reconhecem boa tecnologia e a conquista de seus criadores. E o Django de Adrian é uma dessa conquistas excepcionais que merecem a atenção e o sucesso.&lt;/p&gt;</description><content:encoded><![CDATA[<p><strong>English readers, click <a href="/2008/01/01/chatting-with-adrian-holovaty/">here</a></strong></p>
<p><strong>Traducción en <a href="http://www.marcelor.com/2008/01/conversando-con-adrian-holovaty-creador-del-proyecto-django-traduccion.html"target="_blank" rel="noopener">Español</a></strong></p>
<p>Como prometi depois da entrevista com <a href="http://www.akitaonrails.com/2007/12/15/conversando-com-avi-bryant-parte-1"target="_blank" rel="noopener">Avi Bryant</a> , aqui vai outra grande conversa com <a href="http://www.holovaty.com/"target="_blank" rel="noopener">Adrian Holovaty</a>, o conhecido criador do framework web <a href="http://www.djangoproject.com/"target="_blank" rel="noopener">Django</a> escrito em Python.</p>
<p><img src="http://s3.amazonaws.com/akitaonrails/assets/2007/12/31/387373570_870fa6257c.jpg" alt=""  loading="lazy" /></p>
<p>Para mim esta é uma matéria importante porque eu sempre disse que tecnologia não tem que ser sobre divórcio. Tecnologia é sobre integração. Sou um programador e evangelista Ruby on Rail tempo integral, mas acima de tudo, eu tento ser um ‘bom’ programador. E bons programadores reconhecem boa tecnologia e a conquista de seus criadores. E o Django de Adrian é uma dessa conquistas excepcionais que merecem a atenção e o sucesso.</p>
<p>Então, como meu primeiro post do ano (publicado às 0:01h!), gostaria de celebrar as grandes mentes da nossa comunidade de ‘desenvolvimento’, esperando que os bons desenvolvedores usem seu tempo criando grandes tecnologias em vez de desperdiçar em guerrinhas inúteis.</p>
<p><strong>AkitaOnRails:</strong> Como sempre faço, vamos começar falando um pouco sobre você. Há quanto tempo é programador?</p>
<p><strong>Adrian Holovaty:</strong> Vamos ver – venho lidando com computadores desde que era um garoto brincando com o Commodore 64 no meio dos anos 80.</p>
<p>Um programa que escrevi na época foi para a calculadora TI-85 na classe de matemática do colégio. Quando me graduei um monte de estudantes estavam usando, porque eu tinha todas as fórmulas e coisas assim.</p>
<p>Eu não levei nenhuma dessas coisas a sério até a faculdade, quando estudei ciência da computação. Mas não terminei o curso, que é uma coisa que sempre me incomodou.</p>
<p>Meu curso principal na faculdade foi em jornalismo, pois originalmente eu queria ser um repórter de jornal, mas depois que eu trabalhei com um web site no jornal do campus (<a href="http://www.themaneater.com/"target="_blank" rel="noopener">The Maneater</a> – o melhor nome de jornal que já vi!), percebi que seria muito mais divertido trabalhar online do que com um produto em papel.</p>
<p>Então acabei achando uma maneira de combinar jornalismo/notícias e programação, e eu tive vários trabalhos fazendo exatamente isso!</p>
<p><strong>AkitaOnRails:</strong> E o que o levou ao mundo dos computadores?</p>
<p><strong>Adrian:</strong> Acho que foi minha criação, já que meu pai foi programador de computador desde os anos 70, nos dias do cartão perfurado. Eu lembro de ver todos os tipos de cartões em nossa casa enquanto crescia.</p>
<p><img src="http://s3.amazonaws.com/akitaonrails/assets/2007/12/31/247456700_94dbcdbf86.jpg" alt=""  loading="lazy" /></p>
<p><strong>AkitaOnRails:</strong> E, claro, como conheceu e se apaixonou por Python?</p>
<p><strong>Adrian:</strong> Meu caminho de desenvolvimento web foi (eu acho) bem típico, pelo menos baseado nas experiência de meus amigos e colegas. Eu originalmente aprendi a escrever scripts CGI em Perl em um curso de desenvolvimento web da faculdade. Então eu me ensinei PHP porque alguém me recomendou.</p>
<p>Depois de fazer desenvolvimento Web com PHP por alguns anos, eu meio que me cansei disso. Naquela época, estava trabalhando com Simon Willison no website de um jornal na cidade de Lawrence, no Kansas. Simon e eu estávamos cansados de PHP e decidimos “mergulhar em Python” (trocadilho intencional) lendo o excelente livro de mesmo nome do <a href="http://en.wikipedia.org/wiki/Mark_Pilgrim"target="_blank" rel="noopener">Mark Pilgrim</a> , que foi lançado na época. Isso deve ter sido no fim de 2002.</p>
<p>Imediatamente nos apaixonamos por Python. E quando eu digo “imediatamente”, realmente quero dizer isso. Foi como uma revelação, algum tipo de momento divino. Foi o equivalente em programação à amor à primeira vista.</p>
<p>Então Simon e eu decidimos que, dali em diante, iríamos codificar tudo em Python. Essa é uma das coisas interessantes em se trabalhar em um pequeno grupo de desenvolvimento Web – podíamos tomar esse tipo de decisões instantâneas!</p>
<p><strong>AkitaOnRails:</strong> Eu fui apresentado a Python pela primeira vez acho que em 2000. Mas foi através de desenvolvimento web usando <a href="http://www.zope.org/"target="_blank" rel="noopener">Zope</a>. Acho que ele ganhou muita atenção naquela época. Você já tentou ele?</p>
<p><strong>Adrian:</strong> Não, nunca tentei Zope. Ainda, até hoje, nunca tentei. E me disseram que isso foi uma coisa boa, porque evidentemente ele afastou algumas pessoas de Python na época, seja lá por qual razão. (A versão nova supostamente é muito melhor, mas eu não mexi com ele, também).</p>
<p><strong>AkitaOnRails:</strong> Você está certo, e é meio engraçado porque um monte de pythonistas brasileiros ainda usam <a href="http://plone.org/"target="_blank" rel="noopener">Plone</a> (eu acho que ele evoluiu do Zope mas eu não tentei ele também). Então, todos sabemos que Django nasceu da necessidade quando você estava desenvolvendo websites para a World Online. Por favor nos descreva como você começou e como é o dia-a-dia de trabalho lá. Talvez o que o levou a Django durante seus dias de trabalho.</p>
<p><strong>Adrian:</strong> Claro! Bem, geralmente quando as pessoas pensam em jornais, elas pensam em editores irritados e ultrapassados e repórteres rascunhando em blocos de papel com lápis. Esse jornal do Kansas em que trabalhei era o oposto disso. Está além do escopo desta entrevista, mas por várias razões o website desse jornal atraiu (e ainda atrai até hoje) uma grande equipe de desenvolvimento – uma que eu colocaria contra qualquer equipe 10 vezes o seu tamanho.</p>
<p>Estávamos fazendo coisas “Web 2.0” em 2002 e 2003, e estávamos construindo aplicações web em dias, não semanas ou meses. Isso era principalmente devido ao ambiente jornalístico – pessoal de jornal gosta de datas.</p>
<p>Então estávamos nessa cultura de <em>“desenvolvimento web com datas de jornalismo”</em> e precisávamos de ferramentas que nos deixassem criar aplicações web rapidamente. Olhamos algumas bibliotecas Python que haviam na época (2003), e terminamos decidindo escrever nossas próprias coisas.</p>
<p>Não decidimos parar para fazer um framework – foi um caminho bem clássico e clichê, na realidade! O que aconteceu foi, nós construímos um site com Python. Então construímos mais um, e percebemos que os sites tem uma boa parte de código em comum, então fizemos a coisa certa e extraímos os pedaços comuns em uma biblioteca.</p>
<p>Continuamos a fazer isso – extraindo e extraindo, baseado em cada nova aplicação Web que criávamos – e eventualmente tínhamos um framework.</p>
<p><strong>AkitaOnRails:</strong> Como anotação, realmente isso é meio que interessante! Eu adoraria ouvir o que você acha que a World Online tinha que atraía bons desenvolvedores como você para lá! Apenas um resumo está bom. É o tipo de percepção que empresas brasileiras precisam aprender melhor. Deixá-los usar Python provavelmente foi uma delas :-)</p>
<p><strong>Adrian:</strong> Sim, meio que recai em <strong>dar poder aos empregados</strong>. Meu chefe, que liderava a equipe Web, delegava todas as decisões de tecnologia para mim e o Simon. Esse tipo de cultura realmente encoraja trabalho de qualidade, porque torna todos da equipe mais investidos.</p>
<p><img src="http://s3.amazonaws.com/akitaonrails/assets/2007/12/31/1454328380_6b6053d1c9.jpg" alt=""  loading="lazy" /></p>
<p>￼<br>
<strong>AkitaOnRails:</strong> É verdade. Empresas tradicionais preferem um estilo mais hierárquico (e burocrático), com um gerente idiota segurando o chicote. Mas voltando ao assunto. Então, eu particularmente não gosto desses tipos de comparação linguagem x VS linguagem y ou framework a Vs framework b. Em vez disso gostaria de ouvir o que você acha que é legal sobre Python – a linguagem e a plataforma – e Django. Quais as funcionalidades em Django que você mais gosta, quero dizer, aqueles pedaços que realmente acha para você como “as” grandes idéias?</p>
<p><strong>Adrian:</strong> Bem, eu amo abstrações – como, eu suspeito, qualquer programador. No seu núcleo, Django é somente um conjunto de abstrações comuns às tarefas de desenvolvimento Web. Me dá muito prazer criar abstrações de algo nível, para encapsular problemas maiores de uma maneira que as torna simples.</p>
<p>O que eu realmente gosto sobre Django é a profundidade e extensão das abstrações que ele dá. E em muitos casos (eu espero!), são limpas, fáceis de usar e entender. Deixe-me dar um exemplo de uma abstração: criar um feed RSS.</p>
<p>Quando se cria um feed RSS, você não quer se lembrar de cada tag, da exata formatação do feed – você apenas de importa sobre os itens no feed. Então Django dá uma biblioteca bem simples que recebe um punhado de itens e cria o feed a partir delas.</p>
<p>Isso foi um exemplo óbvio, mas o que realmente me empolga são as abstrações de alto nível, como o conceito de um “site admin”. Django vem com uma aplicação completamente dinâmica que cria um maravilhoso site CRUD pronto para produção para seu banco de dados. Não há código para escrever – é apenas um pouco de configuração opcional. Também tem algo chamado <a href="http://www.djangoproject.com/documentation/databrowse/"target="_blank" rel="noopener"><strong>Databrowse</strong></a>, que é uma abstração do conceito de <em>“Me mostre meus dados, como hiper-textos inteligentes”</em>.</p>
<p>Então, sempre que explico Django a alguém, eu sempre termino dizendo, <em>“é apenas um monte de abstrações de tarefas comuns em desenvolvimento Web”</em> – do encapsulamento de baixo nível de HTTP para conceitos de maior nível. Quanto mais alto nível, mais produtivo você pode ser. Desculpe se isso foi muito conceitual!</p>
<p><img src="http://s3.amazonaws.com/akitaonrails/assets/2007/12/31/1496211063_262b298650.jpg" alt=""  loading="lazy" /></p>
<p>Já sobre Python – o que posso dizer? É maravilhoso. É como poesia. É tão limpo, tão lógico, tão regular, tão óbvio. E o sistema de importação é de morrer.</p>
<p>Muitas pessoas dizem que código Python é fácil de ler/entender por causa da consistência de espaços em branco e simplicidade da linguagem. Eu concordo com isso, mas eu acho que também tem a ver com a elegância do sistema de importação. Por que? Porque se quiser saber como <strong>qualquer</strong> módulo Python funciona, apenas olhe para o código, e olhe para os módulos que ele importa. Como o <a href="http://www.python.org/dev/peps/pep-0020/"target="_blank" rel="noopener">Zen do Python</a> diz, <em>“Namespaces é uma grande idéia buzinante – vamos fazer mais disso!”</em></p>
<p><strong>AkitaOnRails:</strong> Comunidades de tecnologia são legais porque são tão energéticas e apaixonadas. Por um lado você tem aqueles que contribuem e fazem a tecnologia crescer mais depressa. Do outro, tem os espertalhões e trolls que gostam de guerrinhas. Você se queima de vez em quando, ou tende a evitar esse tipo de discussão? Especialmente, algum tempo atrás você e o DHH estiveram juntos na apresentação <a href="http://www.djangoproject.com/snakesandrubies/"target="_blank" rel="noopener">Snakes and Rubies</a>. Eu acho que foi muito legal porque não acabou em mais uma guerrinha. Qual sua opinião sobre esse tipo de discussão aberta e não inflamatória?</p>
<p><strong>Adrian:</strong> A resposta a isso é óbvia – claramente discussões construtivas são mais produtivas.</p>
<p>Por vezes, eu tive minhas paixões por Python/Django tirarem o melhor de mim, mas eu melhorei muito com o tempo. Percebi uma coisa: no fim do dia, o que realmente importa são os sites que as pessoas criam com essas ferramentas, não as ferramentas em si. Se você vai julgar alguém, julgue os sites que essa pessoa faz, em vez das ferramentas que ela usa.</p>
<p>Atualmente, se eu me envolvo em algum tipo de discussão desse tipo, normalmente é para tentar acalmar as pessoas</p>
<p><strong>AkitaOnRails:</strong> Bem dito. Voltando às funcionalidades, você mencionou ‘Databrowse’ antes. Eu gostaria de saber mais sobre isso. Acho que o Django não tinha isso na época em que eu tentei da primeira vez. É uma nova funcionalidade (ou eu estou atrasado?) Poderia elaborar mais essa construção?</p>
<p><strong>Adrian:</strong> Databrowse ainda é pouco conhecido! Aqui vai o Caso de Uso.</p>
<p>Digamos que você tem um monte de dados no seu banco de dados, e tudo que quer é <strong>olhar</strong> para ele. Aqui vão suas opções atuais: você pode cair no console do psql ou mysql e rodar um punhado de SELECTs, mas isso fica cansativo. Você poderia rodar alguma coisa como PHPMyAdmin, mas isso é mais uma ferramenta administrativa do que uma ferramenta de <strong>navegação</strong>. Você poderia usar algum tipo de aplicação externa que permite navegar pelas tabelas do banco, no estilo MS Access, mas isso está no domínio de aplicações de desktop.</p>
<p>Databrowse automaticamente cria um site web que mostra seus dados, de forma que você pode clicar por aí como quiser.</p>
<p>A outra coisa que ele faz é apontar pesquisas interessante e não-óbvias. Por exemplo, se tiver uma tabela que tem uma coluna DATE (tipo Data), ele automaticamente criará uma visão de calendário dessa tabela.</p>
<p>O objetivo não é para as pessoas usarem isso para criar sites públicos – o objetivo é que as pessoas usem isso para explorar seus dados, sem nenhum esforço adicional.</p>
<p>Outro caso de uso vem do mundo do jornalismo. Eu costumava trabalhar para um cara chamado Derek Willis no jornal Washington Post. O trabalho dele era adquirir enormes bancos de dados e colocá-los na intranet do jornal, para que os repórteres pudessem procurar e navegar pelos dados para pesquisas.</p>
<p><img src="http://s3.amazonaws.com/akitaonrails/assets/2007/12/31/179099354_6199af510a.jpg" alt=""  loading="lazy" /></p>
<p>Derek não queria ter que fazer uma nova aplicação Web toda vez que recebesse um novo conjunto de dados, então ele usou Databrowse para fazer sites de intranet que mostrasse seus bancos de dados – com pouco ou nenhum esforço.</p>
<p>Isso volta para o que eu estava dizendo sobre abstrações de alto-nível. Databrowse é um tipo particular de abstração, e é realmente legal que ele incluiu algo assim para as pessoas usarem, se precisarem.</p>
<p><strong>AkitaOnRails:</strong> Parece muito legal, espero poder usar isso logo. Até certo ponto se parece um pouco com <a href="http://dabbledb.com"target="_blank" rel="noopener">Dabble Db</a> – apesar que é dentro da sua aplicação. Acho que você conhece <a href="http://www.akitaonrails.com/2007/12/15/conversando-com-avi-bryant-parte-1"target="_blank" rel="noopener">Avi Bryant</a> ? Acabei de entrevistá-lo e foi uma conversa muito legal. Você já tentou Seaside? (apenas de curiosidade eu tenho uma foto de você e Avi olhando para o macbook dele sentados na grama, o que foi aquilo?)</p>
<p><img src="http://s3.amazonaws.com/akitaonrails/assets/2007/12/19/627957460_48e4181526.jpg" alt=""  loading="lazy" /></p>
<p><strong>Adrian:</strong> Sim, isso foi inspirado no DabbleDB – é essencialmente uma simplificação do DabbleDB para seus próprios dados. Mas eu não diria nem isso, porque DabbleDB é todo um universo mais sofisticado do que Databrowse. DabbleDB é incrível.</p>
<p>Sim, eu me encontrei com Avi diversas vezes e sempre gosto de sua companhia. Até tocamos música juntos – ele é um fantástico tocador de harmônica!</p>
<p>A foto a que você se refere foi tirada na Foo Camp 2007. Eu nunca tentei Seaside, mas está na minha lista de tarefas. Se um dia eu tiver que desenvolver uma aplicação como DabbleDB que precisa manter uma <strong>tonelada</strong> de estado, eu me viraria para Seaside primeiro.</p>
<p><strong>AkitaOnRails:</strong> Entendo, eu sinto que bons músicos tendem a ser bons programadores também. Então, meu primeiro parabéns a você é sobre seu <a href="http://www.amazon.com/Definitive-Guide-Django-Development-Right/dp/1590597257"target="_blank" rel="noopener">novo livro de Django</a> que acabou de ser lançado. Acredito que seja uma grande conquista para a comunidade Django. Mais do que isso você conseguiu torná-lo <a href="http://www.djangobook.com"target="_blank" rel="noopener">disponível online</a> sobre uma licença Creative Commons.</p>
<p>Como você se envolveu com esse livro? Foi difícil convencer sua editora a ter um livro prontamente disponível online? Pergunto isso porque pelo menos no Brasil essa é uma idéia que é MUITO difícil de empurrar para as editoras. Nos conte mais sobre sua experiência escrevendo, os desafios e obstáculos pelo caminho.</p>
<p><strong>Adrian:</strong> Muito obrigado. Não há muita história aqui – o co-conspirador Jacob e eu fomos contatados pelo pessoal da Apress, que estavam interessados em publicar um livro de Django, e fizemos. Bem, devo completar: nos tomou <strong>um longo tempo</strong> , mas fizemos. Não foi difícil convencer a Apress a nos deixar disponibilizar o livro online. Eles tiveram uma experiência anterior fazendo o mesmo com o livro <a href="http://www.diveintopython.org/"target="_blank" rel="noopener">Dive Into Python</a> de Mark Pilgrim e eles são pessoas legais.</p>
<p>Em retrospecto, publicar o livro online foi uma decisão fantástica. Não somente o livro final está disponível online, mas tornamos capítulos disponíveis online enquanto as escrevíamos, com uma funcionalidade muito legal de comentários por parágrafo que deixa os leitores enviar erros, opiniões pelos quais estamos imensamente agradecidos. Tendo experimentado isso, eu não gostaria de publicar um livro de tecnologia de outra maneira.</p>
<p><strong>AkitaOnRails:</strong> Agora, meu segundo parabéns é por causa do <a href="http://www.j-lab.org/batten05winners.shtml"target="_blank" rel="noopener">Batten Award</a> por conquista excepcional em jornalismo online. O <a href="http://www.chicagocrime.org/"target="_blank" rel="noopener">ChicagoCrime.org</a> é muito legal e um bom exemplo de <a href="http://en.wikipedia.org/wiki/Mashup_%28web_application_hybrid%29"target="_blank" rel="noopener">mashup</a> feito direito. Infelizmente nosso governo local e instituições públicas têm muito pouco ou nenhum dado disponível online para usarmos. De onde veio sua idéia? Como foi seu desenvolvimento?</p>
<p><strong>Adrian:</strong> Obrigado – e não deixe isso os assustar de visitar a maravilhosa cidade de Chicago. ￼</p>
<p><img src="http://s3.amazonaws.com/akitaonrails/assets/2007/12/31/adrian_oscon_2006.png" alt=""  loading="lazy" /></p>
<p><strong>AkitaOnRails:</strong> haha, eu moro em São Paulo. Não pode ser pior ;-)</p>
<p><strong>Adrian:</strong> A idéia para o chicagocrime.org veio quando eu estava olhando o web site oficial do <a href="http://www.cityofchicago.org/police"target="_blank" rel="noopener">Departamento de Polícia de Chicago</a> e descobri que eles publicavam dados sobre crimes – embora em uma interface que é mais adequada para procura do que para navegação. Pensei, <em>“uau, são dados muito legais!”</em> e comecei a escrever um extrator de tela em uns 10 minutos.</p>
<p>Na mesma época, eu estava lidando com o site do Google Maps, que havia acabado de ser lançado, para ver se poderia colocar mapas do Google em meus próprios web sites. Eu percebi que dados de crime dariam uma grande aplicação de mapas, então coloquei os dois juntos, e nascia um dos primeiros mashups de Google Maps. Enquanto estava desenvolvendo o site, <a href="http://www.housingmaps.com"target="_blank" rel="noopener">HousingMaps.com</a> (<a href="http://www.craigslist.org/"target="_blank" rel="noopener">Craigslist</a> + Google Maps) saiu, então ele me venceu ao ser o <strong>primeiro</strong> machup real de Google Maps – mas chicagocrime.org veio logo depois.</p>
<p>A coisa que eu mais me orgulho disso foi o fato de chicagocrime.org ter sido um dos sites que influenciou o Google a abrir sua API de mapas. Na época quando os primeiros mashups apareceram, tínhamos que fazer engenharia-reversa do Javascript do Google!</p>
<p><strong>AkitaOnRails:</strong> Sei que você provavelmente tem duas grandes paixões em sua carreira: uma é desenvolvimento baseado em Python, claro, e a outra é jornalismo (não sei a ordem correta aqui). Você criou uma grande reputação na mídia online. Você está trabalhando para o Washington Post agora, correto? Eu li um artigo no <a href="http://www.ojr.org/ojr/stories/060605niles/"target="_blank" rel="noopener">OJR</a> onde você explicou sobre tecnologia sendo usada para dar poderes aos jornalistas. Você pretende ser um repórter algum dia, ou está mais para o back-end do jornalismo? Acho que você tem opiniões fortes sobre o futuro do jornalismo na Era da Internet, não é?</p>
<p><strong>Adrian:</strong> Bem, eu diria que minha <strong>principal</strong> paixão é música, mas é difícil viver disso. (Django foi batizado inspirado no famoso músico de jazz <a href="http://en.wikipedia.org/wiki/Django_Reinhardt"target="_blank" rel="noopener">Django Reinhardt</a>)<br>
￼<br>
Eu não estou mais trabalhando no Washington Post. No meio de 2007 eu ganhei um prêmio de dois anos da <a href="http://www.holovaty.com/blog/archive/2007/05/23/1145"target="_blank" rel="noopener">Knight Foundation</a> para criar um site de notícias local. Então fundei a <a href="http://everyblock.com"target="_blank" rel="noopener">EveryBlock</a> em Julho. Somos uma equipe de quatro pessoas e estamos trabalhando duro para fazer uma aplicação realmente legal.</p>
<p><img src="http://s3.amazonaws.com/akitaonrails/assets/2007/12/31/Picture_2.png" alt=""  loading="lazy" /></p>
<p>Mas sim, voltando à sua pergunta, eu certamente tenho fortes opiniões sobre como o jornalismo é praticado na Internet. Isso provavelmente é fora do escopo dessa entrevista, mas você pode ler meu artigo/reclamação estranhamente entitulado <a href="http://www.holovaty.com/blog/archive/2006/09/06/0307/"target="_blank" rel="noopener">Uma maneira fundamental que sites de notícias precisam mudar</a> para terem uma idéia.</p>
<p><strong>AkitaOnRails:</strong> Agora, isso é novidade para mim. Não sabia que você havia começado sua própria empresa (desculpe, eu ainda preciso alcançar a comunidade Python). Poderia nos dizer mais sobre sua nova empreitada?</p>
<p><strong>Adrian:</strong> Ei, não posso te culpar – estamos nos mantendo fora dos radares. Não há muito a se dizer sobre a EveryBlock neste ponto além do que é um chicagocrime.org vitaminado. É como chicagocrime, mas para mais cidades do que somente Chicado e mais informações do que somente crime.</p>
<p><strong>AkitaOnRails:</strong> Você deveria tentar para o Brasil :-)</p>
<p><strong>Adrian:</strong> Graças à internacionalização do framework Django, isso é inteiramente possível.</p>
<p><strong>AkitaOnRails:</strong> Sim, e falando nisso (fugindo um pouco do assunto) eu recomendo o filme brasileiro <a href="http://en.wikipedia.org/wiki/Tropa_de_Elite"target="_blank" rel="noopener">Tropa de Elite</a> agora que sei que você se interessa por dados relacionados a crimes ;-)</p>
<p><strong>Adrian:</strong> Anotado! Adicionei “Tropa de Elite” à minha lista de FILMES_PARA_ASSISTIR.TXT :-)</p>
<p><a href="http://leahculver.com/"target="_blank" rel="noopener"><img src="http://s3.amazonaws.com/akitaonrails/assets/2007/12/31/1529992544_f8e9fc67b2.jpg" alt=""  loading="lazy" /></a></p>
<p><strong>AkitaOnRails:</strong> Prosseguindo. Muitos grandes websites já são desenvolvidos em Django. Seus próprios trabalhos na World Online, Washington Post sendo alguns deles. Outro grande novo é o <a href="http://pownce.com/leahculver/"target="_blank" rel="noopener">Pownce</a> de <a href="http://en.wikipedia.org/wiki/Kevin_Rose"target="_blank" rel="noopener">Kevin Rose’s</a> (o criador do <a href="http://digg.com"target="_blank" rel="noopener">Digg</a>). Você, Kevin e outras pessoas influentes costumam de encontrar durante conferências, não é? O que acha que o influenciou a tentar Django? Outra coisa legal sobre o Pownce tem que ser <a href="http://leahculver.com/"target="_blank" rel="noopener">Leah Culver</a>. Uma programadora como ela simplesmente inexiste aqui no Brasil, o que é uma pena. Você deveria torná-la símbolo oficial do Django!</p>
<p><strong>Adrian:</strong> Nunca me encontrei com Kevin Rose, mas pelo que ouvi falar, acredito que Leah escolheu Django porque ou foi recomendado a ela ou ela gosta de Python, ou algo assim. Não conheço os bastidores. Também não me encontrei com Leah, mas ela foi legal o bastante para viajar a Lawrence, no Kansas, para nosso último <a href="http://leahculver.com/2007/12/01/django-sprint-part-2/"target="_blank" rel="noopener">Django sprint</a> algumas semanas atrás (que eu não pude ir pessoalmente, infelizmente).</p>
<p><strong>AkitaOnRails:</strong> Não é uma regra escrita em pedra, mas na comunidade Rails nós ‘tendemos’ a gostar de tecnologias relacionadas a Mac. Claro, não é um requerimento para nenhuma linguagem e Linux é tão bom quanto. Mas o que posso dizer? Sou um fã de Apple. Eu gostaria de saber qual é seu ambiente de desenvolvimento. Quais ferramentas você usa para desenvolver websites baseados em Django?</p>
<p><strong>Adrian:</strong> Recentemente mudei para Mac depois de muitos anos usando Linux no desktop, mas não assuma nada lendo isso – não sou um fã, nem de Linux ou Mac. Eu sinto falta do Linux, para ser honesto, e em diversas coisas não me impressionei pelas funcionalidades Unix do OS X. (Coisas básicas como suporte a readline no Python padrão está quebrado, etc).</p>
<p>As coisas legais sobre o Mac fazem valer a pena a mudança, entretanto. Mesmo depois de ter usado Mac como minha máquina principal por 6 meses, eu ainda me impressiono que eu posso somente fechar o laptop e ele dorme automaticamente. Isso nunca funcionou com Linux!</p>
<p><strong>AkitaOnRails:</strong> Pergunto isso porque muitas pessoas olhando para Rails ou Django são programadores Java ou C# acostumados a ter uma IDE completa. Eu normalmente digo que qualquer editor de texto comum faz o serviço, e se realmente quiser poder, vim e emacs se encaixam perfeitamente. O que diz disso?</p>
<p><strong>Adrian:</strong> Eu nunca usei uma IDE para programar, então sou a pessoa errada para perguntar. Na realidade, mais ou menos – eu usei algo como uma IDE na faculdade para programar em assembly. Ele mostrava o conteúdo de acumuladores e coisas assim.</p>
<p><strong>AkitaOnRails:</strong> Oh, quase esqueci de perguntar: você e o <a href="http://www.python.org/~guido/"target="_blank" rel="noopener">Guido van Rossum</a> já se encontraram? Gostaria de saber sua opinião sobre o desenvolvimento do <a href="http://www.python.org/dev/peps/pep-3000/"target="_blank" rel="noopener">Python 3000</a> . Tem alguma coisa na nova versão que você espera muito?</p>
<p><strong>Adrian:</strong> Sim, eu me encontrei com Guido várias vezes e tiver sorte o suficiente para jantar com ele uma vez! É quase idiota e desnecessário dizer isso, mas ele é incrivelmente esperto.</p>
<p>Eu realmente gosto dos novos conceitos e funcionalidades no Python 3000, e eu tive a oportunidade de participar do <a href="http://www.artima.com/weblogs/viewpost.jsp?thread=212259"target="_blank" rel="noopener">sprint no Google</a> Chicago alguns meses atrás. Minha funcionalidade favorita é a migração para strings Unicode por padrão, porque isso força os assuntos sobre encoding.</p>
<p><img src="http://s3.amazonaws.com/akitaonrails/assets/2007/12/31/Picture_3.png" alt=""  loading="lazy" /></p>
<p><strong>AkitaOnRails:</strong> Finalmente, o que foi esse barulho sobre o Django ser versionado 1.0 ou pular direto para 2.0? Não segui as discussões, apenas li os títulos algum tempo atrás. E o que as pessoas podem esperar da próxima versão do Django? Entendo que você não faz grandes lançamentos que mudam o jogo, mas o que você vê sendo lançado nos próximos meses em termos de novas funcionalidades?</p>
<p><strong>Adrian:</strong> Há – essa é uma história engraçada. Eu me cansei das pessoas constantemente perguntando <em>“Quando o 1.0 vai sair? Quando o 10 vai sair?”</em> Minha resposta sempre é, <em>“Por que você precisa de um número arbitrário assinalado no produto? Muitas pessoas estão usando a versão corrente, então não se segure. Números de versões são muito sem sentido.”</em></p>
<p>Então eu sugeri na lista <a href="http://groups.google.com/group/django-developers/browse_thread/thread/b4c237ad76f9eeca/86e2d86b9fa8280f?lnk=gst&amp;q=version&#43;2.0#86e2d86b9fa8280f"target="_blank" rel="noopener">django-developers</a> para assinalar uma versão “2.0” para reforçar o fato que números de versões são de fato arbitrários. Mas, tendo pensado mais sobre isso, mudei de idéia. A quantidade de confusão que isso causaria não justificaria a quantidade de prazer de rebelião que eu teria nisso.</p>
<p>E sobre funcionalidades vindo, existe alguns branches acontecendo no código Django que eventualmente serão unificadas no trunk principal. Um desses branches é chamado <a href="http://code.djangoproject.com/wiki/NewformsAdminBranch"target="_blank" rel="noopener">newforms-admin,</a> que dramaticamente melhora a quantidade de customização que os desenvolvedores precisam fazer para o site admin do Django. Outro é o <a href="http://code.djangoproject.com/wiki/QuerysetRefactorBranch"target="_blank" rel="noopener">queryset-refactor,</a> que é um refatoramento da camada de banco de dados, novamente, para torná-lo mais extensível. Fora isso, existem pequenas funcionalidades que precisamos terminar, e aí será a hora para 1.0.</p>
<p><strong>AkitaOnRails:</strong> Haha, Acho que você está certo, eu não gosto desse ‘culto do ponto-zero’ também, é bem sem sentido para um projeto open source que está em constante evolução. Mas algumas pessoas se preocupam com isso principalmente em ambientes mais corporativos. De qualquer forma, e para pessoas que querem ver Django em ação, quais websites você recomendaria que elas visitem? Alguns do Washington Post, talvez? Claro, algumas das grandes funcionalidades – como o site admin – são apenas para admins (duh) Mas as pessoas gostam de ver coisa ‘vivas’ em vez de um monte de tutoriais.</p>
<p><strong>Adrian:</strong> Apenas para comentar, vejo que o David, do Rails, escreveu <a href="http://www.loudthinking.com/posts/20-dont-overestimate-the-power-of-versions"target="_blank" rel="noopener">uma reclamação</a> sobre esse mesmo tópico.</p>
<p>Bons sites em Django? Vejamos – existe o <a href="http://www.tabblo.com/"target="_blank" rel="noopener">Tabblo</a>, um site de edição e compartilhamento de fotos que tem algumas funcionalidades interativas muito legais. Existe o <a href="http://curse.com"target="_blank" rel="noopener">Curse.com</a>, site de um jogo massivamente online. Tem o <a href="http://lawrence.com"target="_blank" rel="noopener">lawrence.com</a>, um site que eu ajudei a construir, que é um dos melhores sites de entretenimento local do mundo (se posso dizer isso!). E tem um site (esqueci o nome) que imprimirá um livro em papel de uma série de artigos do Wikipedia que você especificar – isso me impressionou muito da primeira vez que vi.</p>
<p><strong>AkitaOnRails:</strong> Então, acho que acabamos :-) Foi uma conversa muito prazerosa. Espero que você tenha um grande Ano Novo!</p>
<p><strong>Adrian:</strong> Obrigado pelas grandes perguntas e a conversa, e Feliz Ano Novo!</p>
<p><img src="http://s3.amazonaws.com/akitaonrails/assets/2007/12/31/Picture_1.png" alt=""  loading="lazy" /></p>
]]></content:encoded><category>entrevistas</category><category>desenvolvimento-web</category></item><item><title>Hanselminutes: Entrevista com David Hansson e Martin Fowler</title><link>https://akitaonrails.github.io/2007/07/08/hanselminutes-entrevista-com-david-hansson-e-martin-fowler/</link><guid isPermaLink="true">https://akitaonrails.github.io/2007/07/08/hanselminutes-entrevista-com-david-hansson-e-martin-fowler/</guid><pubDate>Sun, 08 Jul 2007 00:20:09 -0300</pubDate><description>&lt;p&gt;Como &lt;a href="http://www.akitaonrails.com/articles/2007/04/14/off-topic-seja-arrogante"target="_blank" rel="noopener"&gt;disse antes&lt;/a&gt;, eu ouço muitos podcasts. Todos os dias meu iTunes baixa mais e mais. Isso já me deixou com uma fila enorme de programas ainda a ouvir. Este &lt;a href="http://www.hanselman.com/blog/HanselminutesPodcast65MartinFowlerAndDavidHeinemeierHansson.aspx"target="_blank" rel="noopener"&gt;episódio&lt;/a&gt; do podcast &lt;strong&gt;Hanselminutes&lt;/strong&gt; , em especial, está me esperando desde a RailsConf que aconteceu mês passado.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.flickr.com/photos/computerzen/505364200/"target="_blank" rel="noopener"&gt;&lt;img src="https://akitaonrails.s3.amazonaws.com/files/Scott_Hanselman.jpg" alt="" loading="lazy" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;O host do programa é Scott Hanselman, que na realidade é do mundo .NET, Microsoft, mas que também cobre outras plataformas. Nesse episódio ele estava na RailsConf 2007, em sua cidade natal que é Portland, no Oregon. Ele teve a oportunidade de entrevistar David Heinemeier Hansson e Martin Fowler ao mesmo tempo. Vocês podem ler a transcrição literal da entrevista em &lt;a href="http://perseus.franklins.net/hanselminutes_0065.pdf"target="_blank" rel="noopener"&gt;PDF&lt;/a&gt;.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Como <a href="http://www.akitaonrails.com/articles/2007/04/14/off-topic-seja-arrogante"target="_blank" rel="noopener">disse antes</a>, eu ouço muitos podcasts. Todos os dias meu iTunes baixa mais e mais. Isso já me deixou com uma fila enorme de programas ainda a ouvir. Este <a href="http://www.hanselman.com/blog/HanselminutesPodcast65MartinFowlerAndDavidHeinemeierHansson.aspx"target="_blank" rel="noopener">episódio</a> do podcast <strong>Hanselminutes</strong> , em especial, está me esperando desde a RailsConf que aconteceu mês passado.</p>
<p><a href="http://www.flickr.com/photos/computerzen/505364200/"target="_blank" rel="noopener"><img src="https://akitaonrails.s3.amazonaws.com/files/Scott_Hanselman.jpg" alt=""  loading="lazy" /></a></p>
<p>O host do programa é Scott Hanselman, que na realidade é do mundo .NET, Microsoft, mas que também cobre outras plataformas. Nesse episódio ele estava na RailsConf 2007, em sua cidade natal que é Portland, no Oregon. Ele teve a oportunidade de entrevistar David Heinemeier Hansson e Martin Fowler ao mesmo tempo. Vocês podem ler a transcrição literal da entrevista em <a href="http://perseus.franklins.net/hanselminutes_0065.pdf"target="_blank" rel="noopener">PDF</a>.</p>
<p>Eu achei esta uma das melhores entrevistas dos dois e, como sempre, resolvi que devia traduzir. Porém, essa transcrição está muito literal. Ou seja, está exatamente como eles falaram. O problema disso é que o formato está extremamente informal, disperso, <em>quebradiço</em>, por assim dizer. Eu comecei a traduzir mas o texto ficou tão emaranhado que quase no final resolvi jogar tudo fora e começar novamente. Em vez de traduzir ipsis literis como falaram, vou tentar expôr as idéias principais deles de forma mais organizada para que vocês consigam entender. Mesmo assim, recomendo ouvir o podcast, no áudio a conversa faz mais sentido.</p>
<h2>HTML e CSS<span class="hx:absolute hx:-mt-20" id="html-e-css"></span>
    <a href="#html-e-css" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O primeiro assunto começa quando Scott diz estar cansado de ficar escrevendo tags para criar telas. Ele acha que numa atualidade onde temos efeitos tridimensionais no Linux (com o projeto Beryl), o Mac OS X (que já levou 3D em conta há anos) e o recém-chegado Windows Vista, o visual ‘limitado’ do HTML talvez esteja mostrando sua idade.</p>
<p>David e Martin discordam disso, assim como este <em>humilde</em> tradutor que vos fala. Na realidade HTML e CSS são perfeitos para o trabalho. É um argumento que já foi discutido várias vezes nos últimos anos, mas é inegável que em 10 anos de história, nenhuma outra tecnologia conseguiu fazer frente ao HTML.</p>
<p>Tivemos Applets, o próprio Flash, agora temos tentativas como Apollo, Silverlight. David tem um argumento muito bom: nenhuma dessas ditas ‘alternativas’ apresenta nada de novo. Qual a diferença de um botão em Flash ou um botão em HTML? Absolutamente nenhum, ambos têm os mesmos eventos, fazem as mesmas coisas. A diferença é que em HTML é muito mais simples, padronizado. No fim do dia, um Silverlight talvez tenha efeitos-especiais, enfeites, mas não passa disso: firulas.</p>
<p>No fim do dia, o usuário ainda estará utilizando exatamente os mesmos elementos: vai arrastar o mouse sobre um botão e clicará nele. Vai escolher uma caixa de entrada e digitar seus dados. Vai clicar numa lista e escolher um ítem. Claro, existem os casos extremos, como algum Widget de Mac (ou Gadget, ou Vista), onde você queira ver a planta de sua casa, arrastar os móveis de lugar para visualizar como vai ficar.</p>
<p>Porém, o problema é esse: esse tipo de interface é o caso extremo, a exceção à regra. Somente um caso e centenas. A grande maioria dos casos de aplicação Web é o bom e velho formulário. E foi exatamente para isso que surgiu o HTML e o CSS. Flash pode ser bom para Widgets, não para aplicações Web.</p>
<p>Talvez amanhã, quando tivermos telas multi-touch ou coisas assim, alguém consiga inventar uma maneira completamente nova e muito melhor do que os paradigmas atuais. E o ponto de David é: se isso acontecer, faz sentido ir atrás. Mas enquanto essas coisas não passarem de promessas, não faz sentido segurar a respiração até acontecer. Enquanto isso, HTML e CSS são bons o suficiente para tudo que precisamos hoje.</p>
<h2>“Tela Branca”<span class="hx:absolute hx:-mt-20" id="tela-branca"></span>
    <a href="#tela-branca" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p><img src="https://akitaonrails.s3.amazonaws.com/files/80160232_867ff0523b.jpg" alt=""  loading="lazy" /></p>
<p>Outro excelente argumento de David é contra a “Tela Branca”, no sentido de um quadro em branco que você precisa pintar. A tese é que a maioria das pessoas não se dá bem com telas em branco. O problema é que você tem liberdade de fazer qualquer coisa. Nesse sentido, cada novo projeto seria mais uma tela em branco. Antes de Rails tínhamos que refazer as mesmas coisas o tempo todo. Reinventar a roda. Alguns gostam disso, mas não quer dizer que é a maneira correta.</p>
<p>Novamente, esse é o problema com os ditos ‘candidatos’ a substitutos do HTML. O erro deles é achar que a Web precisa ser reiniciada, começar do zero, começar com outra tecnologia em cima de uma tela branca. Onde está a inovação? Porque começar do zero para fazer os mesmos formulários novamente? Apenas para ter uma animação extra desnecessária? Qual o ponto, senão os casos extremos? Onde está o benefício senão aumentar a confusão?</p>
<p>Como uma nota extra: não precisamos de mais ditos ‘substitutos’. David colocou um ponto interessante: as pessoas buscando a <em>Próxima Grande Coisa</em> são <em>Pesquisadores</em>, mas não podemos confundir pesquisa com desenvolvimento do dia-a-dia. Martin completou bem: desses <em>pesquisadores</em> talvez 5% consigam surgir com algo realmente revolucionário, que talvez valha a pena investir. Obviamente, não temos como saber quem são os 95% que são puro lixo, portanto só nos resta investir nos 100% para descobrir. Mas se ele precisasse chutar iria atrás das pessoas que estão fazendo pesquisas mais aprofundadas no assunto da real interação do usuário com o aplicativo. Não somente artesões de novos ‘botões animados’, mas sim aqueles que estão buscando melhorar o contexto de interface de usuário como um todo.</p>
<p>David dá outros bons argumentos. Veja o AJAX, só agora as pessoas descobriram <em>“Puxa, dá para fazer coisas incríveis com JavaScript manipulando dados em tempo real no browser.”</em> Em bom português, todos se sentiram ‘pintos no lixo’, se esbaldaram e já exageraram. Mesmo com coisas simples como Ajax as pessoas ainda tem muito o que aprender. Não precisamos de nada a mais do que isso neste momento.</p>
<p>Quando você dá telas em branco aos desenvolvedores, eles ficam obcecados. Perdem seu tempo criando enfeites e perdem o foco no verdadeiro objetivo: a interação do usuário com a aplicação. Nós ainda estamos longe de esgotar as possibilidades da tríade HTML, CSS e JavaScript. <em>“Precisamos aprender a andar antes de começar a correr”</em> e nesse assunto ainda estamos engatinhando. Se começarmos a correr desde agora ainda acabamos indo na direção errada. Portanto, não, não precisamos de mais telas em branco ainda.</p>
<p>Como nota à parte de mim mesmo, eu diria que o futuro próximo nas interfaces de usuário não será via um novo plugin vetorial ou applet Java. O correto é puro e simplesmente o <a href="http://www.whatwg.org/specs/web-apps/current-work/"target="_blank" rel="noopener">HTML 5</a>, uma evolução natural do HTML 4 e do XHTML 1.0 que já usamos hoje mas adaptado a aceitar mais eventos, mais propriedades nos elementos que já existem. Muitas das coisas do HTML 5 nós já fazemos hoje, mas para funcionar precisamos embutir quantidades obcenas de JavaScript. A idéia do consórcio formado pela Apple, Mozilla e Opera pode não ser perfeita, mas é uma boa evolução sobre um HTML que não é atualizado significativamente há uma década. Felizmente, parece que os burocratas da W3C finalmente resolveram fazer as peças moverem.</p>
<h2>Web Design<span class="hx:absolute hx:-mt-20" id="web-design"></span>
    <a href="#web-design" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p><img src="https://akitaonrails.s3.amazonaws.com/files/224116549_c1f14e8480.jpg" alt=""  loading="lazy" /></p>
<p>Isso me leva a outro assunto interessante e que a maioria está cansado de saber mas continua fazendo errado. O bom e velho <em>“faça o que eu digo, mas não faça o que eu faço.”</em> Nos processos de desenvolvimento padrão de aplicações Web, primeiro a ‘equipe de design’ faz os ‘protótipos’ da aplicação: fazem uma imagem em Photoshop de cada tipo de página. Dezenas de imagens. Depois disso pronto, passa para alguém recortar e montar em tabelinhas dentro do Dreamweaver.</p>
<p><strong>Errado!</strong> Esta é a maneira mais equivocada de se considerar em um processo. Como David diz: é péssimo usar Photoshop para prototipar a Web pois são mídias completamente diferentes. Fazer cantos arredondados no Photoshop é algo trivial, em HTML e CSS é horrível. É como pedir para um escultor prototipar em argila e depois a coisa real tem que ser feita em metal. Não dá: o designer precisa estar o mais humanamente próximo do metal, moldar diretamente no material final.</p>
<p>A outra maneira errada: fazer o desenvolvedor começar a codificar antes de saber como ela deve ficar. O problema é que o desenvolvedor fará o mais simples, não o melhor. Pois não há referencial a ele. O pensamento mais comum é <em>“farei de qualquer jeito, depois o designer fará ficar bonito”</em>. Isso não funciona. <em>“Mas o designer deixou rosa, com cantos arredondados e tudo bonitinho, não entendo porque o usuário não consegue usar …”</em></p>
<p>Na 37signals os web designers começam fazendo algumas poucas páginas diretamente em HTML. Essa é outra vantagem de HTML e CSS: eles possibilitam colocar ferramentas simples diretamente nas mãos dos designers. No fundo, um web designer não é somente alguém para colocar cores bonitas, mas sim para colocar cores úteis. Um bom Web Designer na realidade é um Designer de Interação.</p>
<p>Uma vez que as primeiras páginas estão prontas, logo elas são convertidas para Rails e a partir daí o designer é obrigado a evoluir as páginas diretamente no HTML, no meio de código Ruby. Isso ainda traz outra vantagem: ninguém precisa conjecturar como a aplicação seria: ela já está funcionando. Agora qualquer mudança é sobre a coisa real. E mais: nada dos horrendos Lorem Ipsum como textos para preencher os vazios da página. Agora o designer pode entrar com dados reais.</p>
<h2>Integração Contínua: Especificações são Inúteis<span class="hx:absolute hx:-mt-20" id="integração-contínua-especificações-são-inúteis"></span>
    <a href="#integra%c3%a7%c3%a3o-cont%c3%adnua-especifica%c3%a7%c3%b5es-s%c3%a3o-in%c3%bateis" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>E como evitar que os designers quebrem o código? Primeiro, temos que parar de considerar que todo designer é tão burro assim. Existem designers burros, mas para começar estes nem deveriam estar trabalhando. Os outros se dão bem trabalhando dessa maneira, sendo ao mesmo tempo designer e usuário. E para garantir a estabilidade do trabalho: testes. Os desenvolvedores tem que criar a suíte de testes desde o começo para evitar que qualquer um quebre as coisas. Quanto mais a aplicação estiver coberta por testes, mais difícil para um designer quebrar o código.</p>
<p>Como Martin bem lembrou, esse processo de designers e desenvolvedores trabalhando juntos levam a iterações muito curtas, coisa de poucas horas ou poucos dias. As decisões são tomadas e implementadas, num processo de integração contínua. Nesse sentido, não existe a burocracia de voltar atrás, buscar alguma especificação funcional e começar mudando de lá: a aplicação em desenvolvimento é a especificação, principalmente porque, como disse David, não existe nada de funcional em uma especificação. A única coisa que se pode chamar de ‘funcional’ é uma aplicação rodando. Não há necessidade de especular <em>“como será que vai ficar”</em> ela literalmente já é, a questão agora é <em>“como melhorar?”</em></p>
<p><img src="https://akitaonrails.s3.amazonaws.com/files/443657782_c8632e2081.jpg" alt=""  loading="lazy" /></p>
<p>São os primeiros passos para um desenvolvimento ágil. Em vez de se preocupar com a burocracia, com especificações, com protótipos, com Photoshops, toma-se uma direção ‘designer-primeiro’. Mas não no sentido que os designer vão se trancar durante três meses e sair do quartinho com dúzias de layouts para passar para os desenvolvedores. As iterações são muito mais curtas, o protótipo passa para uma tela real muito rápido. E, como disse antes, é preciso uma boa dose de disciplina e testes para manter tudo rodando e evoluindo ao mesmo tempo. Parte disso, como descreveu Martin, é que o Rails meio que nos força a criar uma aplicação modularizada, que incentiva a criação de testes.</p>
<h2>Negando novamente a Tela Branca<span class="hx:absolute hx:-mt-20" id="negando-novamente-a-tela-branca"></span>
    <a href="#negando-novamente-a-tela-branca" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Quando começamos um novo projeto, digamos, em Visual C#, a ferramenta nos dá basicamente uma pasta listando as dependências de DLLs e outra pasta simples para códigos-fonte e é só. Uma das coisas que mais agradou Martin no Rails foi o fato dele pré-criar diversas pastas, cada uma com sua função. Rails tenta forçá-lo a fazer a coisa certa dificultando a coisa errada. Para que você faça feio em Rails, é necessário explicitamente querer fazer feio, apagar as pastas que já foram criadas, mover as coisas para lugares errados e assim por diante. Dá tanto trabalho fazer do jeito errado, que ninguém realmente faz isso.</p>
<p>Em .NET existe algo similar em projetos paralelos como o <em>Tree Surgeon</em>, mas a diferença é que na comunidade Rails existe algo no sub-consciente de todos que levam a essa uniformidade. Quando o gerador de Models gera um teste unitário, a idéia é que o desenvolvedor pense <em>“puxa, o gerador criou um arquivo de teste, isso significa que eu deveria estar testando”</em>. Existe motivação para fazer as coisas do jeito certo.</p>
<p>E isso só acontece porque Rails não é uma tela branca. Ela coloca uma série de limitações, ela não o deixa fazer absolutamente tudo que quiser. Pode parecer ilógico, mas a limitação traz mais liberdade de acerto. Isso porque começar numa tela em branco a cada projeto o força a tomar as mesmas decisões várias e várias vezes.</p>
<p>HTML, CSS, Rails todas são tecnologias cujo objetivo é simplificar nossas vidas tomando as decisões repetitivas por nós, limitando nossas possibilidades e com isso aumentando nossa produtividade sem ser difícil de aprender e, principalmente, sem limitar nossa criatividade – como fazem outras tecnologias. Esse é o balanço perfeito.</p>
<h2>Comunidade e Senso de Estética<span class="hx:absolute hx:-mt-20" id="comunidade-e-senso-de-estética"></span>
    <a href="#comunidade-e-senso-de-est%c3%a9tica" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Essa idéia do balanço, das convenções nos leva a outro assunto recorrente: ao senso de estética que parece que todos da comunidade Rails parecem compartilhar. Não basta cuspir código, é preciso saber codificar da mesma forma como um enólogo sabe apreciar um bom vinho.</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/files/383052026_da42ce322c.jpg" alt=""  loading="lazy" /></p>
<p>David diz que uma das habilidades que todo bom programador deve ter é saber dar bons nomes. Veja o recurso de pluralização do Rails. Nós temos essa convenção onde o nome dos Models deve ser no singular e a dos Controllers no plural. Mas não somente acrescentando um “s” no final da palavra. Existe todo um sistema para cobrir praticamente todos os casos da língua inglesa. Houve até um caso famoso na época para ter o plural ‘Octopi’ da palavra singular ‘Octopus’.</p>
<p>É algo que parece fútil, sem sentido, até impensável. Ainda hoje muitos questionam se não teria sido muito mais prático deixar tudo no singular mesmo e pronto. Mas a equipe Rails bateu o martelo: gostamos deste estilo de estética. Eles gastaram um bom tempo para deixar claro que estética importa.</p>
<p>Felizmente para a comunidade Ruby, os primeiros desenvolvedores que adotaram Ruby, como David Thomas, Hal Fulton, eram programadores que também se importavam com o código. E a comunidade se organizou em torno desse mesmo estilo, desse mesmo senso de estética. E o episódio da pluralização do Rails, acabou servindo como um <em>filtro</em> para entrar na comunidade: em vez do programador gastar meses aprendendo Rails para só depois se tocar que na verdade não gosta disso, a pluralização está bem na cara, é uma das primeiras coisas que se vê. Então esse principiante pode escolher imediatamente continuar ou pular fora. Se ele tiver o mesmo estilo, o mesmo nível cultural do resto da comunidade Ruby, vai entender o recurso e vai gostar de Rails.</p>
<p>A idéia não é excluir as pessoas porque gostamos, mas porque podemos: você não vai andar nesta casa se não respeitar esses valores, ou pelo menos se não aprender a apreciá-los.</p>
<h2>Alpha Geeks<span class="hx:absolute hx:-mt-20" id="alpha-geeks"></span>
    <a href="#alpha-geeks" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Finalmente chegamos numa parte mais ‘política’ da discussão. Porque eles começaram dizendo, <em>“o que você acha que tornou .NET popular?”</em>, bem, a Microsoft disse <em>“Esta será a próxima grande coisa.”</em> E o que tornou Java popular? Bem, a IBM e a Sun disseram a mesma coisa. E Ruby? Não há nenhuma corporação por trás dela.</p>
<p>Ruby e Rails cresceram por mérito próprio. Nenhuma grande empresa precisou investir em seu marketing. 37signals? Uma empresa de uma dúzia de pessoas. ThoughtWorks? 800 pessoas. Nada tão extraordinário quanto uma Microsoft com dezenas de milhares de pessoas espalhadas pelo globo.</p>
<p>E falando na ThoughtWorks, o que Martin nos revela é interessante. Ele mesmo diz que sua empresa não é nenhuma mega-corporação. Mesmo assim, quando .NET saiu, eles investiram pesado nele, fizeram diversos projetos. O que eles têm acompanhado é que a demanda pro projetos Java estagnou: não sobe nem desce. Porém as de .NET estão caindo mas Ruby já representa 40% dos projetos da empresa. Ele é o primeiro a dizer que ainda não sabe se isso é só algo momentâneo ou se estamos diante de uma tendência sólida. Mas é algo a se considerar.</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/files/49346772_0ee70562a6.jpg" alt=""  loading="lazy" /></p>
<p>Antes de falar na Microsoft, vamos falar dos Alpha Geeks. Claro, Ruby ainda é apenas um pequeno ponto comparado à larga escala de milhões de desenvolvedores Java e .NET o importante não é apenas o número absoluto de pessoas. O problema é que os chamados ‘Alpha Geeks’ são muito menos, e eles estão migrando de Java e .NET para Ruby e outras linguagens dinâmicas. Quem são esses Alpha Geeks? São os formadores e opinião, são os líderes, os cabeças das comunidades, aqueles que influenciarão as próximas grandes coisas.</p>
<p>O fato dos projetos de .NET estarem em declínio, no exemplo de ThoughtWorks, é uma indicação. É a mesma coisa com os movimentos Ágeis. Se um bom desenvolvedor se vê preso a uma empresa que o obriga a usar processos antiquados waterfall, só lhe resta abandonar essa empresa e ir atrás de alguma outra menor que use XP, por exemplo. Muitas pessoas chegam no David para dizer, <em>“eu larguei meu trabalho por causa de Java. Eu simplesmente não gosto de trabalhar num ambiente onde só usam essas ferramentas. Então preferi pegar me tornar um freelance para trabalhar com Ruby on Rails. Estou muito mais feliz agora.”</em></p>
<p>Mas eles deixam claro uma coisa: isso não quer dizer que Java seja ruim. Muito pelo contrário, eles não desgostam de JRuby. Eles consideram que foi um movimento bastante acertado da Sun ter contratado o pessoal do JRuby e ter investido nesse projeto a ponto de surgir um produto tão bom que já consegue rodar aplicações complexas escritas em Rails. Resta saber se a Microsoft vai pelo mesmo caminho com o .NET.</p>
<h2>Hidra<span class="hx:absolute hx:-mt-20" id="hidra"></span>
    <a href="#hidra" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>E voltamos ao assunto Microsoft. Eles gastam algum tempo conjecturando. O maior problema para a Microsoft é que ela fez tantas coisas ruins que perdeu a boa reputação. Nenhum deles considera que dentro da empresa Microsoft exista indivíduos que sejam verdadeiramente maus. Mas a política da corporação como um todo não está ajudando. Eles filosofam no sentido de que as pessoas podem ser boas pessoas para se conversar, tomar uma cerveja, mas no final são suas atitudes que contam quando alguém publica uma notícia dizendo que o Linux está infringindo não sei quantas patentes.</p>
<p>E o problema para ela é que uma ação má apaga todas as boas ações anteriores. Perdendo a reputação, mesmo tendo coisas realmente interessantes como LINQ, as novas ferramentas de Workflow e tudo mais, mesmo assim a comunidade vai começar a diminuir, os Alpha Geeks vão migrar para outras tecnologias. Talvez seja o que Paul Graham quis dizer quando afirmou que a <a href="http://www.paulgraham.com/microsoft.html"target="_blank" rel="noopener">Microsoft estava morta</a>, no sentido de que ela não é mais perigosa porque perdeu a relevância. Quem sabe?</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/files/521201549_db11ff1e79.jpg" alt=""  loading="lazy" /></p>
<p>O problema da Microsoft em especial, é que ela é muito grande. Quem faz todas essas más ações? Todos concordam que não se pode colocar a culpa de tudo no Bill. Não é ele diretamente quem faz cada uma dessas coisas. O problema é que eles estão sem direção, estão sem líderes. E nesse sentido eles se parecem com uma Hidra. E é por isso que muita gente não confia neles: enquanto uma pessoa lá pode estar sorrindo, outra na mesma empresa o está apunhalando pelas costas.</p>
<p>Como eu disse antes, Scott, o entrevistador, está na folha de pagamento da Microsoft, por isso é uma situação estranha. Ele não pode ser hiperbólico sobre a Microsoft nem pode bater nela. De qualquer forma tentou conciliar: será que não adiantaria para a Microsoft fazer a mesma coisa que pessoas como David e Martin? Sair pelo mundo, em conferências, dizendo as coisas boas que tem. Mas, David, afiado como sempre, deu a última rasteira: <em>“não basta dizer ao mundo que você é bom, você precisa fazer coisas boas para ser considerado bom.”</em></p>
<p>Nós julgamos as pessoas pelos seus atos, não pelo que elas falam. Em um determinado momento, o estilo selvagem e monopolista funcionou muito bem. Eles fizeram bilhões de dólares com essa estratégia. Mas agora não funciona mais. Esse tipo de ‘tradição’ não se muda do dia pra noite: tem pessoas que trabalham lá há mais de 15 anos. Portanto é muito difícil voltar aos trilhos, e eles precisam fazer algo rápido.</p>
]]></content:encoded><category>entrevistas</category><category>desenvolvimento-web</category><category>frontend</category></item><item><title>Coluna RubyOnbr: Acampamento de Curiosos</title><link>https://akitaonrails.github.io/2007/04/14/coluna-rubyonbr-acampamento-de-curiosos/</link><guid isPermaLink="true">https://akitaonrails.github.io/2007/04/14/coluna-rubyonbr-acampamento-de-curiosos/</guid><pubDate>Sat, 14 Apr 2007 14:45:00 -0300</pubDate><description>&lt;p&gt;&lt;strong&gt;Atualização 13-10-2015:&lt;/strong&gt; como o rubyonbr não está mais ativo, agora só via &lt;a href="https://web.archive.org/web/20080905110047/http://rubyonbr.org/articles/2007/04/14/acampamento-de-curiosos/"target="_blank" rel="noopener"&gt;Webarchive&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://akitaonrails.s3.amazonaws.com/files/rubyonbr-logo-site.gif" alt="" loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;Nesse fim de semana saiu minha última coluna na RubyOnBr: &lt;a href="http://forum.rubyonbr.org/forums/4/topics/1234"target="_blank" rel="noopener"&gt;Acampamento de Curiosos&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Desta vez proponho um desafio: entender ou mesmo criar seu próprio web framework. Como base falo do Camping, de _WhyTheLuckyStiff.&lt;/p&gt;
&lt;p&gt;Mas cuidado: sou contra fazer seu próprio framework e achar que ele é o melhor de todos. Todos fazem mais ou menos a mesma coisa (sem ofensas). A idéia aqui é &lt;strong&gt;aprender&lt;/strong&gt; , porque todo bom programador web tem obrigação de saber como seu framework favorito funciona.&lt;/p&gt;</description><content:encoded><![CDATA[<p><strong>Atualização 13-10-2015:</strong> como o rubyonbr não está mais ativo, agora só via <a href="https://web.archive.org/web/20080905110047/http://rubyonbr.org/articles/2007/04/14/acampamento-de-curiosos/"target="_blank" rel="noopener">Webarchive</a></p>
<p><img src="https://akitaonrails.s3.amazonaws.com/files/rubyonbr-logo-site.gif" alt=""  loading="lazy" /></p>
<p>Nesse fim de semana saiu minha última coluna na RubyOnBr: <a href="http://forum.rubyonbr.org/forums/4/topics/1234"target="_blank" rel="noopener">Acampamento de Curiosos</a>.</p>
<p>Desta vez proponho um desafio: entender ou mesmo criar seu próprio web framework. Como base falo do Camping, de _WhyTheLuckyStiff.</p>
<p>Mas cuidado: sou contra fazer seu próprio framework e achar que ele é o melhor de todos. Todos fazem mais ou menos a mesma coisa (sem ofensas). A idéia aqui é <strong>aprender</strong> , porque todo bom programador web tem obrigação de saber como seu framework favorito funciona.</p>
<p>O conceito de todos é o mesmo, as técnicas utilizadas também. Se quiser, é melhor escolher algum já famoso no mercado e participar do projeto como colaborador. Isso é muito mais prático.</p>
<p>E já leram minhas outras colunas na RubnyOnBr? Veja uma lista <a href="http://www.balanceonrails.com.br/pages/rubyonbr"target="_blank" rel="noopener">aqui</a>.</p>
]]></content:encoded><category>desenvolvimento-web</category><category>ruby</category><category>aprendizado</category></item></channel></rss>