<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Software Engineering Blog on rifeli.dev</title><link>https://rifeli.dev/</link><description>Leonardo creates, writes and operates software. 🚀 CTO at Harmo | Data Engineer | Software Architect.</description><generator>Hugo</generator><language>pt-BR</language><copyright>2015 - {year}</copyright><lastBuildDate>Wed, 09 Sep 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://rifeli.dev/index.xml" rel="self" type="application/rss+xml"/><item><title>A métrica que não existia: como tiramos o RRi do zero, na notação</title><link>https://rifeli.dev/blog/2026-09-09-rri-metrica-reputacao-do-zero-necessidade-de-produto/</link><pubDate>Wed, 09 Sep 2026 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/2026-09-09-rri-metrica-reputacao-do-zero-necessidade-de-produto/</guid><description>Uma necessidade de produto chegou sem métrica que a resolvesse: média de estrelas não distingue loja que era boa de loja que é boa. Não tinha o que comprar, então a gente escreveu a matemática. Essa é a jornada do RRi e do método que ficou dela: escrever as propriedades desejáveis e os problemas esperados antes de qualquer símbolo, montar a notação antes do código, revisar a álgebra em vez do programa, trocar o chute por critério de corte declarado, e descobrir que interpretabilidade, e não precisão, é a restrição que decide se a métrica vive.</description><content:encoded><![CDATA[<h2 id="introdução">Introdução</h2>
<p>Entre setembro de 2020 e dezembro de 2022 eu rodei mês a mês a série histórica de uma métrica de reputação que não existia antes da gente escrever. Não tinha fornecedor, não tinha paper pronto pra aplicar, não tinha campo na API do Google esperando pela resposta. Tinha uma necessidade de produto clara, um banco com milhões de avaliações e uma pergunta que a média aritmética não respondia.</p>
<p>O RRi morreu. E eu voltaria a fazer tudo de novo, porque o que ficou na empresa não foi o número, foi a capacidade de pegar uma necessidade de produto e atacar com matemática própria, do zero, sem esperar que alguém publicasse a solução primeiro.</p>
<h2 id="a-necessidade-chegou-sem-métrica">A necessidade chegou sem métrica</h2>
<p>Cliente enterprise de varejo físico não pergunta qual é a nota dele. Ele já sabe a nota. Ele pergunta onde agir na segunda-feira, em qual das centenas de lojas, e por quê.</p>
<p>Média de estrelas não responde isso, por dois motivos. O primeiro é tempo. Uma loja com 4,7 formada por avaliações de 2018 e uma loja com 4,7 formada por avaliações do mês passado são a mesma linha na planilha e não são a mesma loja. A primeira tem um estoque antigo que ninguém confere mais. A segunda tem gente entrando hoje. Quem opera loja sabe a diferença, e a média não sabe.</p>
<p>O segundo é volume. Loja com 12 avaliações e loja com 30 mil aparecem com a mesma autoridade numa tabela ordenada por nota. A lei dos pequenos números faz a nota de quem tem pouco volume chicotear: uma avaliação de 1 estrela num universo de 12 derruba a média em quase meio ponto, e no universo de 30 mil não move o terceiro decimal. Ordenar lojas por média é, em boa parte, ordenar lojas por tamanho de amostra.</p>
<p>A conclusão foi que a gente precisava de uma medida diferente, e que essa medida não existia pronta. Reputação não é média, é estoque. Um acumulado de tudo que já foi dito sobre a loja, com cada avaliação valendo menos conforme envelhece. Isso é uma frase de produto. Virar número é outra história.</p>
<h2 id="antes-da-fórmula-as-propriedades">Antes da fórmula, as propriedades</h2>
<p>A parte da jornada que eu recomendo pra qualquer time que vá construir métrica própria é essa, e ela vem antes de qualquer símbolo: escrever o que a métrica precisa fazer, e escrever também o que vai atrapalhar.</p>
<p>O que a gente exigiu: refletir o estoque de reputação e não a média; decair a importância de cada avaliação no tempo; ter frequência diária, pra dar leitura acionável e não relatório trimestral; permitir agregação temporal e setorial, pra comparar loja com loja, setor com setor, e loja com o próprio setor; e considerar se a avaliação tem texto, porque avaliação escrita carrega informação que estrela sozinha não carrega.</p>
<p>E o que a gente sabia que ia atrapalhar: valor faltante em volume alto; heterogeneidade entre estabelecimentos; heterogeneidade entre setores, porque a régua de nota de restaurante não é a régua de nota de hotel; sazonalidade semanal, porque fim de semana avalia diferente de terça-feira; e interpretabilidade. Guarde esse último item, ele decide o final da história.</p>
<p>Essa lista de dez linhas fez mais pela métrica do que qualquer decisão técnica que veio depois. É ela que transforma &ldquo;queremos medir reputação de verdade&rdquo; em critério de aceite.</p>
<p>Com o tempo essa etapa virou uma ordem fixa, e é ela que eu sigo hoje em qualquer indicador novo. Escrever as propriedades desejáveis e os problemas esperados antes de qualquer símbolo. Montar a notação antes de abrir o editor de código, porque é na álgebra que erro de raciocínio aparece. Revisar a fórmula, e não o programa, até ela responder às propriedades uma por uma. Trocar todo chute de parâmetro por critério declarado, que pode ser discutido e reajustado quando a preferência da empresa mudar. E, no fim, sentar com quem vai receber o número e perguntar o que ele faz na segunda-feira com aquilo. Os cinco passos estão nas seções que vêm, na ordem em que eu aprendi cada um, quase sempre errando antes.</p>
<h2 id="o-primeiro-rascunho-escrito-à-mão">O primeiro rascunho, escrito à mão</h2>
<p>Com as propriedades na mesa, escrevi a primeira versão. O peso de cada avaliação e o índice final ficaram assim, com $s_i$ o escore da avaliação, $\Delta t_i$ o tempo decorrido em dias até a data de referência, $N$ o total de avaliações do conjunto e $\tau_i$ valendo 1 quando a avaliação tem texto:</p>
<div class="formula">
$$ w_i = \frac{\ln(s_i + N + \tau_i)}{\Delta t_i} \qquad\qquad \mathrm{RRi} = 2 \cdot \frac{\sum_i s_i w_i}{\sum_i w_i} $$
</div>
<p>Cada pedaço responde a um item da lista. Média ponderada porque o acumulado tem que somar tudo. Peso caindo com o tempo porque avaliação velha importa menos. Logaritmo pra achatar escala. Bônus pra quem escreveu texto. E um dois no fim pra jogar o resultado numa faixa de 0 a 10, que é uma escala que soa mais natural pra quem lê dashboard.</p>
<p>Virou um microserviço em Go que, por motivos que a minha vaidade da época explica melhor que eu, se chamava Wolfram-Alpha. Ele expunha o índice em três níveis de agregação, estabelecimento, grupo e cliente, e passou a rodar em produção.</p>
<p>Esse é o ponto da jornada que eu quero destacar antes de contar o que deu errado: em poucas semanas a gente saiu de uma insatisfação com a média de estrelas pra um índice diário calculado sobre a base inteira, servido por HTTP, consumido por relatório. Sem contratar ninguém, sem esperar seis meses e sem comprar nada. Métrica própria é acessível. O caro não é começar.</p>
<h2 id="o-que-a-álgebra-devolveu-quando-olhei-de-novo">O que a álgebra devolveu quando olhei de novo</h2>
<p>O caro é revisar. E revisando aquela fórmula com calma, anos depois, dá pra ver três coisas que a intuição não pegou. Vale escrever porque são erros que qualquer um comete na primeira métrica.</p>
<p>O escore aparece dentro do próprio peso. Logaritmo é monotônico crescente, então avaliação de 5 estrelas pesa mais que avaliação de 1 estrela pelo fato de ser 5 estrelas. Numa loja com 5 avaliações, todas com texto, o peso de uma nota 5 fica 23,2% acima do peso de uma nota 1. A regra que eu levo daí é curta: a grandeza que você mede não pode aparecer no peso com que você a mede.</p>
<p>O $N$, que é o total de avaliações do conjunto, é a mesma constante pra todas as avaliações do cálculo. Somar constante de conjunto dentro de um logaritmo que deveria diferenciar itens individuais faz o escore sumir do peso conforme a base cresce: aquela diferença de 23,2% com 5 avaliações cai pra 0,83% com 100, 0,058% com mil e 0,0013% com 30 mil. O esquema de ponderação atua com força máxima onde a amostra é frágil e se dissolve onde ela é robusta, que é o inverso do desejável. Quantidade de conjunto e valor de item não se misturam na mesma expressão.</p>
<p>E o decaimento é $1/\Delta t$, hiperbólico. Avaliação de ontem pesa 365 vezes mais que avaliação de um ano atrás. Como o $\Delta t$ é float de dias, avaliação de seis horas atrás pesa quatro vezes mais que a de ontem, e o peso tende ao infinito quando o tempo tende a zero. Na prática o índice não media estoque, media as últimas 48 horas com uma cauda decorativa, o que contraria a primeira propriedade da lista que eu mesmo escrevi.</p>
<p>Nenhum desses três é erro de programação. Todos os três são visíveis na notação e invisíveis no código, e é por isso que hoje eu escrevo a fórmula antes de escrever a função.</p>
<p>Teve um quarto problema, e esse não estava na álgebra: uma linha de parsing de data em Go descartava o erro, o cálculo seguia com o instante zero e o decaimento desligava em silêncio, o que virava a média ponderada em média simples e continuava exportando número plausível. Não desenvolvo aqui porque não é matemática, é execução, e por isso virou <a href="/blog/2026-09-12-time-parse-go-layout-nao-e-mascara/">post próprio</a>. Do assunto deste texto ele leva uma frase só: métrica não tem teste de sanidade natural, então número errado não vira alerta, vira gráfico.</p>
<h2 id="quando-a-matemática-de-artesão-virou-econometria">Quando a matemática de artesão virou econometria</h2>
<p>O passo seguinte da jornada foi o mais valioso, e foi uma decisão de humildade: a Harmo encomendou um estudo formal pra reescrever o índice, com gente de econometria de verdade. O documento é proprietário e eu não vou reproduzir as equações, mas as correções de rumo são o que importa aqui, e elas mapeiam nos meus três pontos.</p>
<p>O peso passou a depender só do tempo. O escore aparece uma única vez, no numerador, onde ele é objeto da medição e não juiz dela.</p>
<p>O decaimento deixou de ser hiperbólico e virou logarítmico, na forma de um sobre o log do tempo. A diferença é grande na prática: no meu esquema, avaliação de um ano atrás carrega 0,0027 de peso relativo, praticamente nada; no esquema deles, 0,169, sessenta e duas vezes mais. Avaliação velha continua contando, cada vez menos, sem nunca zerar, que é exatamente o que a palavra estoque significa.</p>
<p>E o problema do volume foi atacado onde ele mora, na amostra e não no peso. Em vez de enfiar o $N$ dentro do logaritmo, o estudo define um número mínimo de avaliações pra loja entrar no índice, e escolhe esse número com critério explícito: padroniza desvio-padrão e perda de amostra na mesma escala, traça uma reta de indiferença cuja inclinação é um parâmetro de preferência declarado antes, e toma o ponto mais distante dela. Com preferência neutra, o corte cai em 29 avaliações acumuladas, custa 26,3% da amostra e leva o desvio-padrão mediano de 0,141 pra 0,055.</p>
<p>Essa última parte é a que eu mais admiro no trabalho deles, e é o tipo de coisa que dificilmente sai de dentro da engenharia. Eu tinha resolvido a questão do volume com um logaritmo. Eles resolveram com um critério de decisão declarado, que pode ser discutido, auditado e reajustado quando a preferência da empresa mudar. A diferença entre as duas abordagens não é sofisticação, é rastreabilidade da escolha.</p>
<p>O estudo ainda acrescentou um andar que eu não tinha imaginado: um índice de difusão, que compara cada loja com a média do próprio setor e responde qual porcentagem das lojas está acima dela, numa escala de 0 a 100 com 50 como neutro. Isso resolve de fato a heterogeneidade setorial que estava na minha lista de problemas, e permite comparar restaurante com hotel sem comparar nota com nota.</p>
<h2 id="o-produto-morreu-de-interpretabilidade">O produto morreu de interpretabilidade</h2>
<p>E aí a métrica ficou correta e morreu de qualquer jeito, pelo item que estava na lista desde o começo.</p>
<p>Pense no operador do shopping recebendo a leitura. O RRi da loja dele é 4,42 e a difusão do setor está em 57. O que ele faz na segunda-feira com isso? A escala é nova, não tem faixa de alarme, não tem ação atrelada, e na série real a difusão vive num corredor entre 54 e 59 com ruído diário grande, então a variação que ele vê é indistinguível de barulho.</p>
<p>Compare com a frase que a gente usa hoje: a cada 0,1 estrela adicional, aparece em média uma diferença de 8,8% nos pedidos de rota. É a mesma família de estatística descritiva que eu já mapeei <a href="/blog/2026-07-27-formulas-essenciais-data-science-harmo/">no post sobre as oito famílias de matemática</a>, amarrada num número que o lojista já entende, a estrela, e num resultado que ele quer, gente entrando na porta. Nota é comparável com o concorrente da esquina, é auditável no Google, e todo mundo já sabe se 4,3 é bom ou ruim.</p>
<p>Métrica proprietária cobra um pedágio de aprendizado do cliente, e ela só se paga se, depois de aprender, ele souber o que fazer. O RRi cobrava o pedágio e não entregava a ação. Foi por isso que ele saiu, e não por erro de conta.</p>
<p>Uma ressalva de honestidade, porque ela importa num texto que fala de rigor: essa é a minha leitura, não ata de reunião. Não existe no estudo nenhuma linha dizendo que o índice saiu por interpretabilidade, e o desligamento não teve um dia com data marcada. O que eu afirmo aqui é o que eu vi de dentro. Se quem decidiu lembrar diferente, eu corrijo o post.</p>
<h2 id="o-que-ficou-depois-que-o-produto-saiu">O que ficou depois que o produto saiu</h2>
<p>Quatro coisas que sobreviveram ao RRi, e nenhuma delas é o número.</p>
<ul>
<li>O vocabulário. Reputação como estoque, e não como média, é uma ideia que continua orientando produto aqui. A frase sobrevive à fórmula que a originou.</li>
<li>O critério antes do símbolo. Escrever as propriedades desejáveis e os problemas esperados, dez linhas, antes de qualquer notação. É o artefato mais reaproveitável da jornada inteira e serve pra qualquer métrica nova.</li>
<li>A régua da notação. Os três problemas da minha fórmula eram todos visíveis na álgebra e nenhum era visível no código. Notação é onde erro de raciocínio aparece; código é onde ele fica escondido, rodando e exportando série histórica.</li>
<li>A descoberta de que interpretabilidade era a restrição dominante, e não precisão. Isso custou uma métrica pra aprender e hoje é o filtro que aplico em toda proposta de indicador novo: se não vem com uma ação atrelada, é decoração.</li>
</ul>
<p>Se o critério de sucesso fosse o produto continuar de pé, o RRi foi um fracasso. Mas a empresa saiu da jornada sabendo formular uma métrica própria, sabendo quando escalar isso pra econometria formal, e sabendo que sofisticação estatística não compra interpretabilidade. Isso não estava disponível pra compra, e não teria vindo de outro jeito que não tentando. Necessidade de produto que ninguém no mercado resolve é convite pra escrever a matemática você mesmo, e a conta fecha mesmo quando o resultado é desligado depois.</p>
]]></content:encoded><category>matemática</category><category>data-science</category><category>go</category><category>engenharia</category><category>produto</category></item><item><title>O termo de fronteira</title><link>https://rifeli.dev/blog/2026-09-02-termo-de-fronteira-regra-de-leibniz/</link><pubDate>Wed, 02 Sep 2026 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/2026-09-02-termo-de-fronteira-regra-de-leibniz/</guid><description>Derivar sob o sinal da integral quando os limites também se movem é a regra de Leibniz, e quase todo mundo que aplica ela esquece o termo de fronteira. Num caso que eu conferi numericamente, esquecer esse termo erra a resposta em 4,5 de 11,385, quase 40% do valor. Este post deriva os três termos a partir da regra da cadeia, dá as hipóteses que a troca de derivada com integral exige, mostra onde ela quebra em domínio infinito e no suporte de uma distribuição, e investiga a parte incômoda da história: o que Leibniz de fato escreveu não é a regra que hoje leva o nome dele.</description><content:encoded><![CDATA[<h2 id="introdução">Introdução</h2>
<p>A conta dava 6,885. O valor certo era 11,385. A diferença, 4,5 exatos, não era erro de arredondamento nem de quadratura: era um termo inteiro que eu tinha deixado de escrever. Quase 40% da resposta desaparecida porque a derivada de uma integral tem três pedaços e eu só tinha somado dois.</p>
<p>Derivar integral de limites fixos é entediante: $\int_0^1 g(\tau) d\tau$ é um número, e derivada de número é zero. Fica interessante quando o $t$ que você está derivando aparece em mais de um lugar dentro da integral. O Teorema Fundamental do Cálculo cobre o caso em que ele aparece só no limite. A regra de Leibniz cobre também o caso em que ele aparece dentro do integrando, e os dois ao mesmo tempo, que é o que acontece de verdade quando você modela acumulação com memória.</p>
<p>Na forma geral:</p>
<div class="formula">
$$ \frac{d}{dt}\int_{a(t)}^{b(t)} g(t,\tau)\,d\tau = g\big(t,b(t)\big)\,b'(t) - g\big(t,a(t)\big)\,a'(t) + \int_{a(t)}^{b(t)} \frac{\partial g}{\partial t}(t,\tau)\,d\tau $$
</div>
<p class="formula-nota">Os dois primeiros termos são fronteira: eles medem o efeito de mexer a região de integração. O terceiro é interior: mede o efeito de a curva inteira mudar de altura.</p>
<p>Não vale decorar isso. Vale ver de onde sai, porque de onde sai é regra da cadeia, e aí os três termos param de ser arbitrários.</p>
<h2 id="parte-i-os-três-termos-são-a-regra-da-cadeia-disfarçada">Parte I: os três termos são a regra da cadeia disfarçada</h2>
<p>O truque é parar de ver a integral como função de $t$ e ver ela como função de três argumentos independentes. Define</p>
<div class="formula">
$$ H(t,u,v) = \int_u^v g(t,\tau)\,d\tau $$
</div>
<p>e repara que o que você quer é a derivada de $H(t, a(t), b(t))$, uma composição. A cadeia entrega direto:</p>
<div class="formula">
$$ \frac{dH}{dt} = \frac{\partial H}{\partial v}\,b'(t) + \frac{\partial H}{\partial u}\,a'(t) + \frac{\partial H}{\partial t} $$
</div>
<p>Agora os três pedaços, um por um. $\partial H/\partial v = g(t,v)$ é Teorema Fundamental do Cálculo puro: mexer o limite de cima acrescenta área na borda direita, e a taxa com que ela entra é a altura da curva ali. $\partial H/\partial u = -g(t,u)$ é o mesmo TFC com o sinal trocado, porque subir o limite de baixo tira área. Nenhum dos dois é novidade: você já sabia os dois antes de ouvir falar em Leibniz.</p>
<p>O único pedaço que é teorema de fato é o terceiro, $\partial H/\partial t = \int_u^v \partial_t g(t,\tau) d\tau$. Ele diz que dá pra trocar a ordem de duas operações de limite, derivar e integrar, e essa troca é justamente o que precisa de hipótese pra valer. É esse pedaço que se chama derivar sob o sinal da integral. A regra de Leibniz é ele mais dois termos de fronteira que já vinham de graça. E isso não é atalho didático meu: é assim que a prova é montada nas notas de Keith Conrad, com a versão de limites variáveis saindo como corolário da versão de limites fixos, aplicando a regra da cadeia exatamente a essa função de três argumentos.</p>
<p>A intuição geométrica fecha o assunto. Entre $t$ e $t+dt$ a integral muda por dois motivos independentes. A região de integração cresce, e nasce uma tira fina de área na borda, de largura $dt$ e altura igual ao integrando naquele ponto. E a curva inteira sobe ou desce, em todo ponto do interior, porque o integrando também depende de $t$. Pensa numa piscina que fica ao mesmo tempo mais comprida e mais funda: o volume novo é a fatia que entrou na ponta mais o que subiu no resto do fundo. Somar os dois efeitos é a regra.</p>
<figure class="diagrama">
<svg viewBox="0 0 400 210" role="img" aria-labelledby="diagrama-titulo diagrama-desc" xmlns="http://www.w3.org/2000/svg">
  <title id="diagrama-titulo">De onde vêm os três termos da regra de Leibniz</title>
  <desc id="diagrama-desc">Uma curva desenhada sobre um eixo horizontal, com a região de integração delimitada pelos limites a de t, à esquerda, e b de t, à direita. Quando t cresce, três coisas acontecem ao mesmo tempo. O limite de baixo anda para a direita e uma tira estreita de área sai da região: contribuição negativa. O limite de cima anda para a direita e uma tira estreita de área entra: contribuição positiva. E a curva inteira sobe entre os dois limites, indicada por uma segunda curva tracejada acima da primeira, acrescentando uma banda de área no interior: contribuição também positiva.</desc>
  <path class="area-base" d="M 95 103.1 L 113.8 96.7 132.5 91.1 151.2 86.5 170 83 188.8 80.9 207.5 80 226.2 80.5 245 82.3 263.8 85.4 282.5 89.7 301.2 95.1 320 101.3 L 320 170 L 95 170 Z"/>
  <path class="area-int" d="M 95 89.7 L 113.8 82 132.5 75.3 151.2 69.8 170 65.6 188.8 63 207.5 62 226.2 62.6 245 64.8 263.8 68.5 282.5 73.7 301.2 80.1 320 87.6 L 320 101.3 301.2 95.1 282.5 89.7 263.8 85.4 245 82.3 226.2 80.5 207.5 80 188.8 80.9 170 83 151.2 86.5 132.5 91.1 113.8 96.7 95 103.1 Z"/>
  <path class="area-sai" d="M 95 103.1 L 106.5 99.1 118 95.3 L 118 170 L 95 170 Z"/>
  <path class="area-entra" d="M 320 101.3 L 332.5 105.9 345 110.7 L 345 170 L 320 170 Z"/>
  <path class="guia" d="M 95 103.1 L 95 170 M 320 101.3 L 320 170"/>
  <path class="curva" d="M 40 125 L 61.2 116.2 82.5 107.8 103.8 100 125 93.2 146.2 87.6 167.5 83.4 188.8 80.9 210 80 231.2 80.9 252.5 83.4 273.8 87.6 295 93.2 316.2 100 337.5 107.8 358.8 116.2 380 125"/>
  <path class="curva-nova" d="M 95 89.7 L 113.8 82 132.5 75.3 151.2 69.8 170 65.6 188.8 63 207.5 62 226.2 62.6 245 64.8 263.8 68.5 282.5 73.7 301.2 80.1 320 87.6"/>
  <path class="eixo" d="M 30 170 L 385 170"/>
  <path class="seta" d="M 97 181 L 116 181 M 112 178 L 116 181 L 112 184"/>
  <path class="seta" d="M 322 181 L 343 181 M 339 178 L 343 181 L 339 184"/>
  <text class="sinal sinal-sai" x="106.5" y="140" text-anchor="middle">−</text>
  <text class="sinal sinal-entra" x="332.5" y="142" text-anchor="middle">+</text>
  <text class="sinal sinal-int" x="207.5" y="77" text-anchor="middle">+</text>
  <text class="rotulo" x="95" y="199" text-anchor="middle">a(t)</text>
  <text class="rotulo" x="320" y="199" text-anchor="middle">b(t)</text>
</svg>
<figcaption>Os dois limites andam para a direita e a curva sobe. O limite de baixo subindo <em>tira</em> área, e é daí que vem o sinal negativo de \(-g(t,a(t))a^{\prime}(t)\); o limite de cima subindo <em>põe</em> área; e a banda entre as duas curvas é o termo interior, \(\int \partial_t g\), o único dos três que precisa de hipótese pra existir.</figcaption>
</figure>
<h2 id="as-hipóteses-e-onde-continuidade-deixa-de-bastar">As hipóteses, e onde continuidade deixa de bastar</h2>
<p>A versão suficiente pra quase tudo é curta: se $g$ e $\partial g/\partial t$ forem contínuas num retângulo que contenha a região de integração, e $a$ e $b$ forem diferenciáveis, a fórmula vale. Num retângulo fechado a continuidade já dá limitação de graça, e é isso que faz a demonstração andar.</p>
<p>Em domínio infinito, continuidade não basta, e o contraexemplo é curto o suficiente pra caber numa linha:</p>
<div class="formula">
$$ I(t) = \int_0^\infty t\,e^{-tx}\,dx $$
</div>
<p>Para todo $t &gt; 0$ isso vale exatamente 1, e você confere na mão: a primitiva é $-e^{-tx}$, avaliada de 0 a infinito dá 1, e o $t$ cancela. Em $t = 0$ o integrando é identicamente zero, então $I(0) = 0$. O integrando é suave em $t$ para cada $x$ fixo, e mesmo assim $I$ é descontínua na origem. Derivar sob o sinal ali não produz erro pequeno, produz besteira: a função nem é contínua no ponto.</p>
<p>A massa fugiu. Conforme $t$ diminui, a exponencial se espalha e fica baixinha, mas a área não muda, só migra pra longe. Nenhuma função integrável fixa segura a família toda por cima, e é exatamente isso que a dominação pede. No enunciado que o Conrad usa, pra derivar num ponto $t_0$: $g$ e $\partial_t g$ contínuas, e cotas $|g(t,\tau)| \le A(\tau)$ e $|\partial_t g(t,\tau)| \le B(\tau)$ independentes de $t$, com $A$ e $B$ integráveis. Repara no detalhe que muda tudo na prática: essas cotas são pedidas só para $t$ num intervalo em volta de $t_0$. A dominação é local no parâmetro, não é um contrato que você assina para todo $t$ do universo.</p>
<p>E é condição suficiente, não necessária. A versão de Lebesgue, que sai da convergência dominada, cobra menos: basta $g(\cdot,t)$ integrável para um $t$ e a cota integrável em cima da derivada, $|\partial_t g(t,\tau)| \le B(\tau)$, numa vizinhança de $t_0$. A cota em $g$ você não precisa trazer, ela cai do teorema do valor médio aplicado à própria derivada. Fica a regra de bolso: em retângulo compacto a continuidade compra a dominação; em domínio infinito você traz a dominação de casa, e traz menos do que parece.</p>
<h2 id="um-caso-inteiro-com-o-erro-medido">Um caso inteiro, com o erro medido</h2>
<p>Pega um acumulador com memória logarítmica, que é a forma que aparece quando o passado pesa cada vez menos mas nunca zera:</p>
<div class="formula">
$$ A(t) = A_0 + \int_0^t F(\tau)\,\log_2\!\big(2+t-\tau\big)\,d\tau $$
</div>
<p class="formula-nota">O parâmetro \(t\) aparece nos dois lugares que interessam: no limite de cima e dentro do núcleo. É o caso completo da regra, com um dos termos de fronteira ativo.</p>
<p>Identificando as peças: $a(t) = 0$ com $a^{\prime} = 0$, o que mata o termo de fronteira de baixo; $b(t) = t$ com $b^{\prime} = 1$; e $g(t,\tau) = F(\tau)\log_2(2+t-\tau)$.</p>
<p>O termo de fronteira de cima é $g(t,t) \cdot 1 = F(t)\log_2 2 = F(t)$. Ele saiu redondo por construção: o núcleo vale exatamente 1 na diagonal. A derivada parcial do núcleo é $\partial_t \log_2(2+t-\tau) = 1/((2+t-\tau)\ln 2)$, e escrevendo $c = 1/\ln 2$:</p>
<div class="formula">
$$ A'(t) = F(t) + c\int_0^t \frac{F(\tau)}{2+t-\tau}\,d\tau $$
</div>
<p>A regularidade aqui é confortável: $g$ e $\partial_t g$ são contínuas no triângulo compacto $\lbrace 0 \le \tau \le t \le T\rbrace $ e o denominador nunca desce abaixo de 2. Nada perto de explodir, nenhuma dominação pra negociar.</p>
<p>Sobrou uma integral do mesmo formato, então aplica Leibniz de novo. O novo termo de fronteira é o núcleo na diagonal, $F(t)/2$, e a parcial do novo núcleo ganha um sinal negativo:</p>
<div class="formula">
$$ A''(t) = F'(t) + c\left[\frac{F(t)}{2} - \int_0^t \frac{F(\tau)}{(2+t-\tau)^2}\,d\tau\right] $$
</div>
<p>Agora a parte que interessa: conferir. Com $F(\tau) = 1 + \tau/2$ e $t = 7$, comparando a expressão de Leibniz contra a derivada numérica da integral obtida por quadratura de Simpson e diferença finita central:</p>
<table>
  <thead>
      <tr>
          <th></th>
          <th>numérico</th>
          <th>fórmula de Leibniz</th>
          <th>erro</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>$A^{\prime}(7)$</td>
          <td>11,385154864385</td>
          <td>11,385154864821</td>
          <td>4,4e-10</td>
      </tr>
      <tr>
          <td>$A^{\prime\prime}(7)$</td>
          <td>1,745261940300</td>
          <td>1,745261949709</td>
          <td>9,4e-09</td>
      </tr>
  </tbody>
</table>
<p>Os dois erros são do método, não da fórmula. A coluna de Leibniz bate com a forma fechada da integral na décima quinta casa, então o que a tabela mostra é erro da derivada numérica, e ele erra pelo motivo esperado. Variando o passo, aparece o desenho de sempre: o erro da diferença central cai como $h^2$ até perto de $h = 10^{-4}$ e depois volta a subir como $1/h$, quando subtrair dois números quase iguais passa a custar mais do que a aproximação ganha. A diferença segunda faz a mesma curva, só que o ramo de arredondamento sobe como $1/h^2$. É por isso que os dois passos da tabela são diferentes, cada um perto do fundo da curva do seu método, e por isso a segunda linha erra mais mesmo usando passo cem vezes maior.</p>
<p>Todas as contas deste post estão num script de verificação em Python, só biblioteca padrão, que imprime método, discretização, valor esperado, valor obtido, erro absoluto e tolerância, e sai com código diferente de zero se qualquer verificação passar da tolerância justificada pro método dela: <a href="/code/2026-09-02-termo-de-fronteira-regra-de-leibniz/verificacao.py">verificacao.py</a>. São 23 verificações e roda em um segundo e meio.</p>
<p>E o número que abriu o post: esquecendo o termo de fronteira, $A^{\prime}(7)$ daria 6,885154864821 em vez de 11,385154864821. A diferença é 4,5 exatos, que é exatamente $F(7)$, o termo que ficou de fora. Não é erro pequeno, é 39,5% da resposta. E não podia ser pequeno: o termo de fronteira vale $g(t,b(t))b^{\prime}(t)$, uma quantidade da ordem do próprio integrando. Quem esquece ele não perde precisão, perde o resultado.</p>
<p>A segunda armadilha é mais boba e mais comum: usar a mesma letra para a variável de integração e para o parâmetro. Escrever $\int_0^t F(t) dt$ não é notação relaxada, é expressão sem sentido, e o efeito colateral pior é apagar a possibilidade de aplicar a regra. Se as duas coisas têm o mesmo nome, você não consegue nem perguntar qual está variando.</p>
<h2 id="onde-isso-aparece-em-computação">Onde isso aparece em computação</h2>
<p>Nada disso é exclusividade de integral com primitiva fechada. A regra aparece em lugares que qualquer pessoa que escreve código encontra, e em dois dos três casos abaixo o termo de fronteira não é detalhe: é a resposta inteira.</p>
<p>O primeiro é a janela deslizante, que é o agregado mais comum de qualquer sistema de observabilidade. Soma dos últimos $W$ minutos é uma integral com os dois limites andando junto com o tempo:</p>
<div class="formula">
$$ A(t) = \int_{t-W}^{t} f(\tau)\,d\tau \qquad\Longrightarrow\qquad A^{\prime}(t) = f(t) - f(t-W) $$
</div>
<p class="formula-nota">O integrando não depende de $t$, então o termo interior é zero e sobra fronteira pura: o que entra na janela menos o que sai dela.</p>
<p>Conferi numericamente e bate na décima primeira casa. A consequência é conhecida de quem já pôs alarme em cima da variação de uma métrica de janela: cada pico produz dois eventos, um quando entra e outro, exatamente $W$ depois, quando sai. A queda que aparece uma janela inteira depois do incidente não é o sistema melhorando, é o segundo termo de fronteira entrando na conta com sinal trocado. O gráfico está certo e a leitura é que erra, porque a intuição trata a curva como se fosse o sistema quando ela é o sistema convolvido com a janela.</p>
<p>O segundo é o gradiente de uma esperança, que é o que treina boa parte de machine learning moderno. Otimizar a esperança de $f(X)$ sob uma densidade $p_\theta$, em relação a $\theta$, é derivar uma integral cujo parâmetro está na densidade, e a pergunta de sempre é se dá pra empurrar a derivada pra dentro. Quando o suporte não depende de $\theta$, dá, e o que sobra é a identidade que sustenta os estimadores de gradiente por amostragem, o mesmo movimento que aparece em policy gradient:</p>
<div class="formula">
$$ \nabla_\theta \int f(x)\,p_\theta(x)\,dx = \int f(x)\,\nabla_\theta \log p_\theta(x)\,p_\theta(x)\,dx $$
</div>
<p class="formula-nota">Essa identidade não é universal, e vale saber o que ela está cobrando: suporte independente de \(\theta\), regularidade suficiente pra trocar derivada com integral, e \(f\) sem dependência direta do parâmetro. Se o custo também depender, \(f_\theta\), aparece um segundo termo, \(\mathbb{E}[\nabla_\theta f_\theta(X)]\). A primeira das três hipóteses é a que quebra no exemplo seguinte.</p>
<p>Conferi com $X \sim N(\mu,1)$, $f(x)=x^3$ e $\mu = 1{,}7$: a identidade e a derivada numérica dão as duas 11,67, que é o valor fechado $3(\mu^2+1)$, com erro de $5{,}3 \times 10^{-15}$ e $2{,}2 \times 10^{-10}$.</p>
<p>Agora o caso em que o suporte depende do parâmetro, que é onde a coisa fica interessante. Tome $X \sim \mathrm{Uniforme}(0,\theta)$ e $f(x) = x^2$. A esperança sai na mão, e a derivada dela também:</p>
<div class="formula">
$$ \mathbb{E}[X^2] = \int_0^\theta \frac{x^2}{\theta}\,dx = \frac{\theta^2}{3} \qquad\Longrightarrow\qquad \frac{d}{d\theta}\,\mathbb{E}[X^2] = \frac{2\theta}{3} $$
</div>
<p>Em $\theta = 3$ isso vale 2, e esse é o número a bater. Agora aplique a identidade de cima do jeito ingênuo: derive só a densidade, com $x$ fixo, esquecendo que o suporte também é função de $\theta$. No interior a densidade é $1/\theta$, então $\partial_\theta(1/\theta) = -1/\theta^2$, e a conta fecha em outro lugar:</p>
<div class="formula">
$$ \underbrace{\int_0^\theta x^2\left(-\frac{1}{\theta^2}\right) dx}_{\text{interior}} = -\frac{\theta}{3}, \qquad \underbrace{f(\theta)\,p_\theta(\theta)}_{\text{fronteira}} = \frac{\theta^2}{\theta} = \theta, \qquad -\frac{\theta}{3} + \theta = \frac{2\theta}{3} $$
</div>
<p class="formula-nota">O interior sozinho dá \(-1\) em \(\theta = 3\): não é imprecisão, é sinal trocado. Os 3 que faltam são o termo de fronteira, o limite de cima andando junto com o parâmetro. Somados, os dois devolvem o gradiente certo.</p>
<p>Vale desfazer uma confusão que eu mesmo já fiz aqui. É tentador culpar &ldquo;autodiff ingênuo&rdquo;, mas o autodiff não tem opinião: ele deriva o que você entregou. Se o que você entregou foi a densidade, e você derivou ela ignorando que o domínio se move, erra. Se o que você entregou foi o programa de amostragem, $X = \theta U$ com $U \sim \mathrm{Uniforme}(0,1)$, acerta, e acerta por um motivo que é o assunto deste post: a mudança de variável $x = \theta u$ tira o parâmetro da fronteira e coloca no integrando.</p>
<div class="formula">
$$ \mathbb{E}[f(X)] = \int_0^1 f(\theta u)\,du $$
</div>
<p>Com os limites presos em 0 e 1, não sobra fronteira nenhuma pra esquecer, o termo interior dá conta de tudo sozinho, e derivar $f(\theta u)$ é regra da cadeia de primeiro semestre. Esse é o gradiente pathwise, o truque de reparametrização. A diferença entre ele e o score function não é esperteza de implementação: é onde cada um deixa o parâmetro. O score function deixa $\theta$ na medida, e aí a fronteira móvel vira problema dele. O pathwise empurra $\theta$ pro integrando, e o problema deixa de existir.</p>
<p>Esse caso é o exemplo padrão da falha na literatura. Mohamed, Rosca, Figurnov e Mnih usam a mesma $\mathrm{Uniforme}(0,\theta)$, com $f(x)=x$, no survey de estimação de gradiente por Monte Carlo, e o diagnóstico deles é mais preciso do que &ldquo;esqueceu um termo&rdquo;: o score function assume continuidade absoluta, que $p_{\theta+h}$ seja positiva onde $p_\theta$ é, e essa hipótese quebra na borda exatamente quando o parâmetro define o suporte. Na conta deles o gradiente verdadeiro é $1/2$ e o estimador devolve $-1/2$. E quando eles refazem a mesma esperança por derivada de medida, a separação que aparece é literalmente a regra de Leibniz: $f(\theta)/\theta$ na fronteira, menos $(1/\theta^2)\int_0^\theta f$ no interior.</p>
<p>O terceiro é rendering diferenciável, e é o exemplo mais bonito porque a fronteira ali é literalmente uma silhueta. A cor de um pixel é uma integral, e o termo de visibilidade torna o integrando descontínuo: um ponto está atrás do objeto ou não está. A posição dessa descontinuidade depende dos parâmetros da cena, então derivar em relação à geometria produz uma contribuição concentrada na borda. Quem ignora essa contribuição consegue treinar cor e material, e não consegue mover geometria, porque o gradiente que empurraria a silhueta é justamente o que ficou de fora. O trabalho que virou referência na área ataca isso de frente, com um algoritmo de amostragem de arestas que, nas palavras dos autores em tradução minha, &ldquo;diretamente amostra as funções delta de Dirac introduzidas pelas derivadas do integrando descontínuo&rdquo;.</p>
<p>Some os três e o padrão fica claro. Na integral fechada da seção anterior, esquecer a fronteira custou um pedaço da ordem do próprio integrando. No suporte móvel, custa o sinal. No rendering, custa a capacidade de otimizar geometria. O termo que ninguém precisa provar continua sendo o que mais cobra.</p>
<h2 id="o-parâmetro-que-você-inventa">O parâmetro que você inventa</h2>
<p>A mesma regra sustenta um truque que parece mágica na primeira vez: introduzir um parâmetro que não existia no problema, só pra derivar em relação a ele. O exemplo clássico cabe em três linhas.</p>
<div class="formula">
$$ I(a) = \int_0^1 \frac{x^a - 1}{\ln x}\,dx $$
</div>
<p>Essa integral não tem primitiva elementar. A derivada dela tem, e é trivial: derivar sob o sinal mata o logaritmo do denominador, porque $\partial_a x^a = x^a \ln x$. Sobra $I^{\prime}(a) = \int_0^1 x^a dx = 1/(a+1)$, e como $I(0) = 0$, integrando de volta vem $I(a) = \ln(a+1)$, válido para $a &gt; -1$.</p>
<p>E aqui eu escorreguei escrevendo este post, então conto o escorregão. A tabela-resumo do Conrad traz as dominantes prontas para exatamente esta integral, com $|\partial_a f| = x^a \le 1$. Peguei a cota e segui em frente. Só que aquela linha da tabela vale para $0 &lt; a &lt; c$, e eu tinha acabado de afirmar o resultado para todo $a &gt; -1$. Com $a$ negativo a cota é falsa: em $x = 0{,}01$ e $a = -1/2$, $x^a$ já vale 10. Cota tabelada vale no intervalo em que foi tabelada.</p>
<p>A conta que cobre o domínio inteiro é local, que é como a dominação tinha que ser desde o começo. Fixe $a_0 &gt; -1$ e um intervalo $[c,C]$ em volta dele que contenha o zero, com $c &gt; -1$. Em $(0,1)$ a função $x^s$ decresce em $s$, então para todo $a$ nesse intervalo:</p>
<div class="formula">
$$ \left|\frac{\partial f}{\partial a}\right| = x^a \le x^c, \qquad |f(x,a)| = \left|\int_0^a x^s\,ds\right| \le (C-c)\,x^c, \qquad \int_0^1 x^c\,dx = \frac{1}{c+1} $$
</div>
<p class="formula-nota">A cota do meio sai de enxergar o próprio integrando como uma integral, \(\int_0^a x^s\,ds = (x^a-1)/\ln x\), e é ela que resolve as duas pontas de uma vez: limitar numerador e denominador em separado não funciona perto de \(x=1\), onde \(1/\lvert\ln x\rvert\) explode e é só o numerador indo a zero que segura o quociente.</p>
<p>Nenhuma das duas depende de $a$, as duas são integráveis porque $c &gt; -1$, e as duas valem numa vizinhança de $a_0$, não no domínio inteiro de uma vez. É a mesma dominação local da seção das hipóteses, agora numa integral concreta.</p>
<p>Conferi em cinco valores de $a$, quatro positivos e um negativo. Nos positivos o erro fica em $1{,}1 \times 10^{-7}$, todo ele de quadratura junto às pontas, onde o integrando vale $-1/\ln x$ e tem derivadas de ordem alta ilimitadas, e o Simpson deixa de valer $O(h^4)$. Com $a$ negativo o integrando é ilimitado em zero e a integral é imprópria, então o script troca de variável, $x = u^p$ com $p(a+1) &gt; 1$, e o erro cai para $10^{-14}$.</p>
<p>É esse movimento que ficou conhecido como truque de Feynman. E é aqui que o post vira outro post, de propósito. Até agora eu tratei a regra como ferramenta, e ferramenta se julga por funcionar. Daqui pra frente eu trato ela como atribuição, e atribuição se julga por ser verdadeira. A segunda pergunta tem uma resposta bem pior do que eu esperava quando comecei a procurar.</p>
<h2 id="parte-ii-o-que-leibniz-de-fato-escreveu">Parte II: o que Leibniz de fato escreveu</h2>
<p>Gottfried Wilhelm Leibniz nasceu em 1 de julho de 1646 em Leipzig e morreu em 14 de novembro de 1716 em Hannover. Ele não era matemático de formação: doutorou-se em direito em Altdorf, em fevereiro de 1667, e ganhou a vida como secretário, advogado, bibliotecário e conselheiro de corte. A matemática entrou por via diplomática. Foi a Paris em 1672 numa missão política, ficou até 1676, estudou com Christiaan Huygens no outono de 1672 e, por indicação dele, foi ler Saint-Vincent sobre soma de séries.</p>
<p>A notação é a parte da herança que ninguém disputa. Em 29 de outubro de 1675, num manuscrito não publicado chamado <em>Analyseos tetragonisticae pars secunda</em>, ele escreveu a frase que vale a pena ler no original: &ldquo;Utile erit scribi ∫ pro omnia, ut ∫l = omn. l, id est summa ipsorum l&rdquo;. Será útil escrever ∫ em vez de <em>omnia</em>, isto é, a soma dos próprios $l$. O símbolo é um S alongado de <em>summa</em>, e ele nasceu como abreviação de uma palavra que Leibniz estava cansado de escrever. Duas semanas depois, em 11 de novembro de 1675, no manuscrito <em>Methodi tangentium inversae exempla</em>, aparecem $dx$, $dy$ e $dy/dx$. E um detalhe de rodapé sobre como as datas escorregam: a biografia de Leibniz no MacTutor dá 21 de novembro de 1675 para a primeira aparição de $\int f(x)dx$, enquanto a página de primeiras ocorrências de símbolos, no mesmo arquivo, data de 11 de novembro o manuscrito em que ele pela primeira vez pôs $dx$ depois do símbolo de integral, que é o mesmo evento descrito com outras palavras. As duas páginas não batem. O que dá pra dizer é que elas não têm o mesmo lastro: a segunda cita Cajori, volume 2, página 204, e a primeira não cita fonte. Isso não resolve, mas diz onde se resolve, e eu deixo o endereço: os dois manuscritos estão na edição crítica da Academia, série VII, volume 5, páginas 288 a 295 e 321 a 331.</p>
<p>O artigo de 1684 na <em>Acta Eruditorum</em>, o <em>Nova methodus pro maximis et minimis</em>, é a primeira publicação do cálculo diferencial. Ele traz a notação $d$ e as regras de derivada de potência, produto e quociente, e não traz demonstração nenhuma. Os resultados de cálculo integral saíram em 1684 e 1686 com o nome de <em>calculus summatorius</em>; a expressão cálculo integral foi sugestão de Jacob Bernoulli, em 1690. E o símbolo $\int$, inventado em 1675, só apareceu impresso em 1686.</p>
<p>A disputa de prioridade com Newton rende um parágrafo, e ele é bom. Em 1711 Leibniz leu um artigo de Keill acusando ele de plágio e recorreu à Royal Society. Newton, que era presidente da Royal Society, nomeou um comitê &ldquo;imparcial&rdquo; para decidir se o inventor do cálculo era ele ou Leibniz, escreveu ele mesmo o relatório oficial do comitê, sem assinar, e depois publicou uma resenha anônima do próprio relatório nas <em>Philosophical Transactions</em>. O relatório saiu como <em>Commercium Epistolicum</em> no começo de 1713, e Leibniz só viu o documento no outono de 1714.</p>
<p>Aqui começa a parte em que eu preciso ser chato. A continuação dessa história, do jeito que circula, é que o rancor da disputa isolou a matemática britânica por um século, e que a culpa foi de os ingleses se agarrarem aos pontinhos de Newton em vez do $d$ de Leibniz. Quem estudou o período de perto contesta. Niccolò Guicciardini, no livro que dedicou ao cálculo de fluxões na Grã-Bretanha entre 1700 e 1800, sustenta que a diferença entre a notação de Newton e a de Leibniz recebeu importância demais: as duas escolas compartilhavam um método matemático comum, com dois algoritmos que se traduziam um no outro, e passar de uma notação pra outra era trivialidade. E rastreia a imagem deprimente do cálculo newtoniano até os escritos da Analytical Society de Cambridge, no começo do século XIX, reformadores com interesse direto em pintar o passado como atrasado. A disputa não foi inofensiva. Mas a versão forte da lenda tem origem identificável e autor interessado, e a lista de matemáticos britânicos do século XVIII, com Taylor, Stirling, Bayes, Maclaurin e Simpson dentro, não parece lista de terra arrasada.</p>
<p>O que se afirma com tranquilidade sobre notação é mais modesto e mais interessante que a lenda: Leibniz sabia que achar boa notação era problema de primeira ordem e pensava muito nisso, enquanto Newton escrevia mais para si mesmo e usava a notação que lhe ocorria no dia. O $d$ e o $\int$ deixam explícito o aspecto de operador, e foi esse aspecto que os desenvolvimentos posteriores foram cobrar.</p>
<p>Agora a pergunta que me interessava, e que é a razão de este post existir: Leibniz enunciou a regra que leva o nome dele?</p>
<p>A resposta honesta é não, não nessa forma. O que a literatura documenta é mais estreito, e antes de eu contar o que é, o grau de certeza: eu li isso no resumo do capítulo 2 da monografia, o que o editor publica na página do livro, e não no volume impresso. Com a ressalva na mesa. Steven Engelsman, na monografia sobre a origem da diferenciação parcial a partir de problemas de famílias de curvas, situa em 1697 a descoberta e o uso, por Leibniz e Johann Bernoulli, do que ele chama teorema da permutabilidade entre derivação e integração, na forma $d_x\int \varphi(x,a) da = \int d_x \varphi(x,a) da$, com Jakob Bernoulli usando a mesma propriedade em 1698. O contexto era o problema das trajetórias ortogonais, e os dois estavam conscientes da novidade conceitual: chamaram a operação de diferenciação de curva em curva, em contraste com a derivação ordinária ao longo de uma única curva.</p>
<p>Olha bem o que está e o que não está nessa fórmula. Está o terceiro termo, a troca de ordem, o pedaço que é teorema de fato. Não estão os limites variáveis, não estão os dois termos de fronteira, não está hipótese nenhuma. O que Leibniz teve em 1697, pelo que a documentação sustenta, foi a permutação com limites fixos, escrita na linguagem de famílias de curvas, e usada como ferramenta para resolver um problema geométrico concreto. A regra completa que hoje leva o nome dele, com os três termos e com o retângulo de continuidade, é construção posterior de outras pessoas.</p>
<p>E aqui tem uma ironia de bibliografia que vale contar. As notas de Keith Conrad sobre derivação sob o sinal da integral, que são hoje a referência que todo mundo manda para quem pergunta, abrem dizendo que o método é &ldquo;devido a Leibniz em 1697&rdquo;, e a nota de rodapé dessa afirmação aponta para uma resposta no History of Science and Mathematics Stack Exchange. Não é crítica ao Conrad, o texto dele é de matemática e é excelente. É um retrato de como funciona a atribuição em matemática: a data circula com precisão de ano, a cadeia de citação termina num fórum, e ninguém checa porque o crédito não muda a fórmula.</p>
<p>O rigor que hoje se exige veio depois, e veio de outro projeto. Leibniz operava com infinitesimais, quantidades menores que qualquer grandeza dada e ainda assim não nulas, e nem ele tinha certeza do estatuto delas. Numa carta a Johann Bernoulli de junho de 1698 escreve, em tradução livre minha do inglês, que talvez o infinito e o infinitamente pequeno que concebemos sejam imaginários, mas adequados para determinar coisas reais, assim como as raízes também costumam ser consideradas imaginárias. É o autor do cálculo dizendo, com elegância, que não sabe se os objetos dele existem.</p>
<p>A formulação moderna abandonou o infinitesimal como objeto e reconstruiu tudo em cima de limite, com Bolzano, Cauchy e Weierstrass, e foi essa reconstrução que trouxe as hipóteses. A troca de derivada com integral deixou de ser propriedade evidente e passou a exigir continuidade, convergência uniforme e, com a integral de Lebesgue, convergência dominada, que é a hipótese que dá conta do contraexemplo da seção anterior sem pedir nada além de uma cota integrável.</p>
<p>Fecha o círculo o Feynman, responsável pela regra ter fama de arma secreta. Em <em>Surely You&rsquo;re Joking, Mr. Feynman!</em>, no capítulo &ldquo;A Different Box of Tools&rdquo;, ele conta que o professor de física do colégio, o Mr. Bader, mandou ele ficar depois da aula e deu de presente um livro, o <em>Advanced Calculus</em> do Woods, porque ele falava demais e fazia barulho por tédio. E conta o efeito: &ldquo;aquele livro também mostrava como derivar parâmetros sob o sinal da integral, é uma certa operação. Acontece que isso não é muito ensinado nas universidades, elas não dão ênfase nisso&rdquo;. Foi com essa peça que ele construiu a fama de resolver integrais em Princeton, não por ser mais rápido, mas porque a caixa de ferramentas dele tinha algo que as outras não tinham. A regra é de 1697, o rigor é do século XIX, e a fama é de um físico que ganhou um livro de presente por ser aluno inconveniente.</p>
<p>O termo de fronteira é o resumo do post. É a parte da regra que ninguém precisa provar, que qualquer um deduz do TFC, e é justamente a parte que todo mundo esquece de escrever. Vale 40% da resposta no caso que eu conferi. A parte difícil você respeita, porque ela impõe hipótese e te obriga a pensar. A parte fácil você atropela, e é ela que te cobra a conta.</p>
<h2 id="fontes">Fontes</h2>
<ul>
<li>Biografia de Leibniz, arquivo MacTutor, St Andrews: datas de nascimento e morte, doutorado em direito em Altdorf, os anos de Paris e o contato com Huygens, conteúdo do <em>Nova methodus</em> de 1684, primeira aparição impressa do ∫ em 1686, cronologia da disputa e do <em>Commercium Epistolicum</em>. <a href="https://mathshistory.st-andrews.ac.uk/Biographies/Leibniz/">mathshistory.st-andrews.ac.uk/Biographies/Leibniz</a></li>
<li>Biografia de Newton, arquivo MacTutor: comitê &ldquo;imparcial&rdquo; da Royal Society, relatório escrito por Newton sem assinatura e resenha anônima do próprio relatório. <a href="https://mathshistory.st-andrews.ac.uk/Biographies/Newton/">mathshistory.st-andrews.ac.uk/Biographies/Newton</a></li>
<li><em>Earliest Uses of Symbols of Calculus</em>, compilação de Jeff Miller hospedada no MacTutor, citando Cajori vol. 2 p. 204: manuscrito de 29 de outubro de 1675 com a frase sobre o ∫, manuscrito de 11 de novembro de 1675 com $dx$, $dy$ e $dy/dx$, e a informação de que os dois foram publicados primeiro por Gerhardt e hoje estão na edição crítica da Academia, <em>Sämtliche Schriften und Briefe</em>, Reihe VII vol. 5, <em>Infinitesimalmathematik 1674-1676</em>, Akademie Verlag, 2008, pp. 288-295 e 321-331. <a href="https://mathshistory.st-andrews.ac.uk/Miller/mathsym/calculus/">mathshistory.st-andrews.ac.uk/Miller/mathsym/calculus</a></li>
<li><em>The rise of calculus</em>, MacTutor: postura de Leibniz e de Newton em relação a notação, <em>calculus summatorius</em>, e a sugestão do nome cálculo integral por Jacob Bernoulli em 1690. <a href="https://mathshistory.st-andrews.ac.uk/HistTopics/The_rise_of_calculus/">mathshistory.st-andrews.ac.uk/HistTopics/The_rise_of_calculus</a></li>
<li>Niccolò Guicciardini, <em>The Development of Newtonian Calculus in Britain, 1700-1800</em>, Cambridge University Press, 1989: crítica à tese do declínio britânico, origem da imagem na Analytical Society de Cambridge, e a posição de que as duas escolas compartilhavam um método comum com notações traduzíveis uma na outra. Parafraseado, não citado; ver a seção seguinte.</li>
<li>Steven B. Engelsman, <em>Families of Curves and the Origins of Partial Differentiation</em>, North-Holland Mathematics Studies 93, 1984: 1697 como data da descoberta e uso do teorema de permutabilidade por Leibniz e Johann Bernoulli, 1698 para Jakob Bernoulli, e o termo diferenciação de curva em curva. Conferido no resumo do capítulo 2, <em>Families of Curves in the 1690s</em>, publicado pela editora, e não no volume impresso. <a href="https://www.sciencedirect.com/bookseries/north-holland-mathematics-studies/vol/93/suppl/C">sciencedirect.com/bookseries/north-holland-mathematics-studies/vol/93</a></li>
<li>Carta de Leibniz a Johann Bernoulli de 7/17 de junho de 1698, edição da Academia A III 7, 796-97, em tradução inglesa publicada pela Technion: os infinitos e infinitamente pequenos como possivelmente imaginários. <a href="https://humanities.technion.ac.il/wp-content/uploads/2023/07/Bernoulli-Leibniz.English-2.pdf">humanities.technion.ac.il</a></li>
<li>Keith Conrad, <em>Differentiating under the integral sign</em>: o Teorema 12.3, com a dominação pedida só para $t$ num intervalo em volta do ponto; o Corolário 12.4, que tira a versão de limites variáveis do teorema de limites fixos mais a regra da cadeia sobre $I(t,a,b)$; a seção 8, que é a integral do truque de Feynman deste post, com as dominantes na Tabela 2; a atribuição a &ldquo;Leibniz em 1697&rdquo; na primeira frase, cuja nota de rodapé é a referência [4], uma resposta no History of Science and Mathematics Stack Exchange; e a referência [16], a edição de 1934 do Woods pela Ginn and Co. A epígrafe é o trecho do Feynman. <a href="https://kconrad.math.uconn.edu/blurbs/analysis/diffunderint.pdf">kconrad.math.uconn.edu</a></li>
<li>Shakir Mohamed, Mihaela Rosca, Michael Figurnov e Andriy Mnih, <em>Monte Carlo Gradient Estimation in Machine Learning</em>, JMLR 21 (2020) 1-62: as condições para a troca de derivada e integral no estimador score function (seção 4.3.1), a hipótese de continuidade absoluta e o exemplo de suporte limitado com $\mathrm{Uniforme}(0,\theta)$ e $f(x)=x$, com gradiente verdadeiro $1/2$ contra $-1/2$ do estimador (seção 4.3.2), a derivação do estimador pathwise empurrando o parâmetro pro custo (seção 5.2), e a separação em termo de fronteira e termo interior na equação (41a). <a href="https://jmlr.org/papers/volume21/19-346/19-346.pdf">jmlr.org/papers/volume21/19-346</a></li>
<li>Tzu-Mao Li, Miika Aittala, Frédo Durand e Jaakko Lehtinen, <em>Differentiable Monte Carlo Ray Tracing through Edge Sampling</em>, ACM Transactions on Graphics 37(6), SIGGRAPH Asia 2018: descontinuidade de visibilidade no integrando e amostragem direta das deltas de Dirac introduzidas pela derivada. A citação do parágrafo de rendering vem do resumo dos autores. <a href="https://people.csail.mit.edu/tzumao/diffrt/">people.csail.mit.edu/tzumao/diffrt</a></li>
<li>Richard Feynman, <em>Surely You&rsquo;re Joking, Mr. Feynman!</em>, capítulo &ldquo;A Different Box of Tools&rdquo;: o Mr. Bader, o <em>Advanced Calculus</em> do Woods, e o trecho sobre derivar parâmetros sob o sinal da integral.</li>
<li>Conferência numérica de tudo que este post afirma em número: $A^{\prime}(7)$ e $A^{\prime\prime}(7)$ contra a forma fechada, o termo de fronteira esquecido valendo $F(7)$, a janela deslizante, o gradiente de esperança com suporte fixo, os cinco números da $\mathrm{Uniforme}(0,\theta)$, o contraexemplo $\int_0^\infty t e^{-tx}dx$ e a integral $\int_0^1 (x^a-1)/\ln x,dx = \ln(a+1)$. São 23 verificações, quadratura de Simpson com 200 mil a 400 mil subintervalos somada com <code>math.fsum</code>, diferenças finitas centrais, uma troca de variável para o caso impróprio, e uma tolerância própria por método. Script em Python, só biblioteca padrão. <a href="/code/2026-09-02-termo-de-fronteira-regra-de-leibniz/verificacao.py">verificacao.py</a></li>
</ul>
<h2 id="o-que-eu-não-consegui-verificar">O que eu não consegui verificar</h2>
<ul>
<li><strong>Se Leibniz chegou perto da forma com limites variáveis.</strong> O que achei documentado é a permutação com limites fixos, em 1697. Nenhuma fonte que eu alcancei diz que ele tratou dos termos de fronteira nesse contexto, e nenhuma diz que ele não tratou. Fica em aberto.</li>
<li><strong>Quem primeiro enunciou a regra na forma moderna, com hipóteses.</strong> Não achei fonte. Sei dizer que a maquinaria veio da rigorização do século XIX e que a versão com convergência dominada é posterior a Lebesgue, mas não atribuo o primeiro enunciado a ninguém porque não sei.</li>
<li><strong>A monografia do Engelsman e a edição Gerhardt eu não li.</strong> A afirmação sobre 1697 vem do resumo do capítulo 2 publicado pela editora, não do volume impresso, e não fui à correspondência original. O exemplar está no Internet Archive, mas em regime de empréstimo, sem busca no texto. Quem quiser fechar essa lacuna precisa do livro na mão.</li>
<li><strong>A formulação exata do Guicciardini.</strong> A posição dele sobre a notação ter recebido importância demais eu confirmei em fonte secundária, e por isso ela aparece aqui como paráfrase, não entre aspas. Não tenho a página, e não vou fingir que tenho.</li>
<li><strong>A divergência de data entre duas páginas do MacTutor</strong>, 11 contra 21 de novembro de 1675, continua sem resolução. O que eu consegui apurar é que as duas não têm o mesmo lastro documental: a página de símbolos cita Cajori, a biografia não cita nada. Quem for atrás dos manuscritos tem o endereço na lista acima, edição da Academia, série VII volume 5.</li>
<li><strong>Qual edição do Woods o Feynman tinha.</strong> A bibliografia do Conrad cita a edição nova, de 1934, da Ginn and Co. O Feynman não dá edição no relato. Não sei qual era.</li>
</ul>
]]></content:encoded><category>matemática</category><category>educação</category></item><item><title>Ensinei o Claude Code a documentar minha sprint no ClickUp (e ele abriu a última task sozinho)</title><link>https://rifeli.dev/blog/2026-08-26-skill-clickup-claude-code-documentar-sprint/</link><pubDate>Wed, 26 Aug 2026 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/2026-08-26-skill-clickup-claude-code-documentar-sprint/</guid><description>Numa única conversa saíram 15 tasks pontuadas, 11 subtasks reestimadas e dois pull requests, sem eu abrir o ClickUp na mão. Este post abre a skill inteira: como ela é montada, o caminho entre o pedido em linguagem natural e a task criada, os quatro casos de escrita nos campos de ponto da API do ClickUp, o erro da minha primeira implementação que só apareceu quando fui escrever isso aqui, o modo de falha parcial que produzia exatamente a inconsistência que a skill queria evitar, o fallback de credencial que troca a identidade da operação em silêncio, e a lista honesta do que ela ainda não tem.</description><content:encoded><![CDATA[<img id="image-custom" src="/images/posts/f973e008-4073-4ae7-ba2c-2ab7ab37e399.png" alt="Cartões de task saindo de um terminal, passando por um portão de aprovação e chegando a um board" />
<p id="image-legend">Da conversa pro board: a skill propõe as tasks pontuadas, eu aprovo, ela escreve.</p>
<p>Numa única conversa com o Claude Code saíram 15 tasks pontuadas, 11 subtasks reestimadas e dois pull requests. Eu não abri o ClickUp em momento nenhum. No fim, a parte que mais me marcou foi banal: a própria ferramenta que eu tinha acabado de construir criou a última task do board, sozinha, fechando o loop.</p>
<p>Esse post é a história de como cheguei nisso e, principalmente, o que eu encontrei quando fui abrir a implementação pra escrever aqui. Não é um tutorial de como criar uma skill do zero. Esse eu já contei no post do <a href="/blog/2026-06-17-slash-command-save-session-claude-code/">/save-session</a>. Aqui o assunto é o que acontece quando você dá a uma skill o poder de escrever num sistema que o time inteiro usa, e o que essa escrita pode quebrar quando ela falha no meio.</p>
<h2 id="por-que-o-board-ficava-pra-trás">Por que o board ficava pra trás</h2>
<p>Trabalho de infraestrutura quase sempre acontece antes da burocracia. Você investiga um custo, mexe num cluster, prepara um upgrade de banco, e só depois lembra que aquilo precisa virar registro em algum lugar. O registro fica pra trás. A sprint não reflete o que foi feito. O esforço some.</p>
<p>Abrir task no meio do trabalho dá preguiça porque dá trabalho de verdade: título, escopo, pontos, épico, sprint certa, atribuição. Multiplica por quinze e ninguém faz. Foi exatamente o que aconteceu comigo numa sessão de trabalho de custo: eu tinha um monte de coisa feita e planejada, e zero disso no board.</p>
<p>A virada foi parar de tratar a documentação como uma segunda jornada e passar a tratar como subproduto. Eu conversei com o Claude Code enquanto fazia o trabalho de verdade, e no fim pedi pra ele documentar.</p>
<h2 id="do-curl-improvisado-pra-uma-skill">Do curl improvisado pra uma skill</h2>
<p>Não comecei com uma skill. Comecei com a coisa mais burra que funcionava: pedi pro Claude descobrir como a gente já falava com o ClickUp. Ele varreu o código, achou uma integração existente que abre task automaticamente quando o <a href="/blog/2026-06-03-relatorios-custo-aws-cronjob-eks/">robô diário de custo AWS</a> detecta anomalia, entendeu o padrão da API e passou a montar chamadas a partir dali. Em poucos minutos eu tinha tasks sendo criadas via <code>curl</code> improvisado.</p>
<p>Funcionou, e era frágil pelo motivo mais chato: a cada novo pedido o modelo redescobria os mesmos IDs, os mesmos campos, a mesma convenção. Redescoberta é onde mora a variação, e variação em escrita é onde mora o erro. Empacotei numa <a href="https://code.claude.com/docs/en/skills">skill do Claude Code</a>, que é um diretório com um <code>SKILL.md</code> que carrega só quando é usado:</p>
<pre tabindex="0"><code>~/.claude/skills/clickup-ops/
├── SKILL.md        # instrucao pro modelo: quando usar, fluxo, regras combinadas
├── reference.md    # ids estaveis do workspace e mapeamento dos custom fields
└── scripts/cu.py   # o que fala com a API, sem modelo no meio do caminho
</code></pre><p>O <code>SKILL.md</code> tem frontmatter com <code>name</code> e <code>description</code>, e é a <code>description</code> que faz o modelo puxar a skill quando eu falo de sprint, ponto ou task. O corpo dele não é código: é o combinado com o time escrito em português, e cada regra dali vira uma das seções abaixo.</p>
<p>O <code>reference.md</code> guarda o que é estável e caro de descobrir: ids de workspace, space e folder de sprints, o meu user id, e o mapeamento dos custom fields com o UUID de cada opção. Tem uma linha ali que vale mais que o resto do arquivo: as list ids de sprint <strong>não</strong> são estáveis, mudam a cada quinzena, e por isso são proibidas de virar constante.</p>
<p>O <code>cu.py</code> é o único que toca a rede, e nenhum subcomando dele pede opinião. A regra que eu segui é simples de enunciar e chata de manter: <strong>o modelo decide o quê, o script decide como</strong>. Escolher o ponto de uma feature é julgamento. Montar payload, resolver UUID de opção e escolher endpoint é determinístico, e determinístico não se delega pra um gerador de texto.</p>
<h2 id="de-documenta-isso-até-a-task-criada">De &ldquo;documenta isso&rdquo; até a task criada</h2>
<p>O pedido chega em linguagem natural, do tipo &ldquo;abre essas tasks na sprint&rdquo;. A primeira coisa que a skill faz é descobrir a sprint corrente, e o método é mais rústico do que eu gostaria: ela lista o folder de sprints, extrai as datas do <strong>nome</strong> de cada lista com regex e escolhe a que contém hoje. Quando nenhuma cobre o dia, ela não chuta, avisa e manda usar o backlog ou uma lista explícita. Depende de convenção de nomenclatura e quebra se alguém renomear uma sprint. Aceito o acoplamento porque a alternativa, cravar id, quebra a cada quinze dias em silêncio.</p>
<p>Resolvida a lista, o resto sai do <code>reference.md</code>: épico, tipo, responsável e o UUID da opção de ponto. Aí ela monta o plano e me mostra antes de escrever qualquer coisa:</p>
<pre tabindex="0"><code>Sprint 25/08 a 05/09  (lista 9011xxxxxx)

  5 pts  Infra     Tecnica  Corrigir alarme que nao dispara em fila parada
  3 pts  Cronjobs  Tecnica  Migrar job diario de custo pro agendador novo
  2 pts  Infra     Debito   Remover credencial de servico do script legado

  3 tasks | 10 pts | status todo
</code></pre><p>Eu aprovo ou ajusto, e só então ela sobe. Depois da escrita, imprime o id e a URL de cada task, que é o que a resposta da API devolve. Não há releitura pra conferir se o board ficou como o plano dizia.</p>
<h2 id="os-dois-campos-de-ponto-e-o-que-eu-errei-na-primeira-implementação">Os dois campos de ponto, e o que eu errei na primeira implementação</h2>
<p>O ClickUp tem dois lugares diferentes pra guardar ponto, e eles não conversam. Tem o custom field que o time configurou no board, um dropdown, e tem o <strong>Sprint Points</strong> nativo, que é o que alimenta o relatório de velocity. Preencher só um deixa o board mostrando um número e o relatório contando outro.</p>
<p>A skill escreve nos dois, sempre iguais. A parte que eu errei foi <em>como</em>.</p>
<p>Eu implementei a criação em duas etapas: o <code>POST</code> criava a task já com o custom field, e um <code>PUT</code> posterior preenchia o Sprint Points. Ao revisar a implementação pra escrever este texto, descobri que a separação não era necessária na criação. O <a href="https://developer.clickup.com/reference/createtask">endpoint de criação</a> aceita <code>points</code> e <code>custom_fields</code> no mesmo payload:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>POST /api/v<span style="color:#bd93f9">2</span>/list/{list_id}/task
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">&#34;name&#34;</span>: <span style="color:#f1fa8c">&#34;Corrigir alarme que nao dispara em fila parada&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">&#34;description&#34;</span>: <span style="color:#f1fa8c">&#34;Escopo da task.\n\n---\nPS: task aberta automaticamente pelo Claude Code.&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">&#34;status&#34;</span>: <span style="color:#f1fa8c">&#34;todo&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">&#34;assignees&#34;</span>: [&lt;user_id&gt;],
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">&#34;points&#34;</span>: <span style="color:#bd93f9">5</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">&#34;custom_fields&#34;</span>: [
</span></span><span style="display:flex;"><span>    {<span style="color:#ff79c6">&#34;id&#34;</span>: <span style="color:#f1fa8c">&#34;&lt;uuid do campo Pontos&gt;&#34;</span>, <span style="color:#ff79c6">&#34;value&#34;</span>: <span style="color:#f1fa8c">&#34;&lt;uuid da opcao 5&gt;&#34;</span>},
</span></span><span style="display:flex;"><span>    {<span style="color:#ff79c6">&#34;id&#34;</span>: <span style="color:#f1fa8c">&#34;&lt;uuid do campo Epic&gt;&#34;</span>,   <span style="color:#ff79c6">&#34;value&#34;</span>: <span style="color:#f1fa8c">&#34;&lt;uuid da opcao Infra&gt;&#34;</span>}
</span></span><span style="display:flex;"><span>  ]
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>A separação em duas chamadas só é obrigatória em outro caso, o de <strong>repontuar uma task que já existe</strong>. O <a href="https://developer.clickup.com/reference/updatetask">endpoint de atualização</a> aceita <code>points</code>, mas não aceita <code>custom_fields</code>, e a documentação é explícita ao dizer que pra atualizar Custom Fields você precisa usar o endpoint específico:</p>
<pre tabindex="0"><code>POST /api/v2/task/{task_id}/field/{field_id_pontos}   {&#34;value&#34;: &#34;&lt;uuid da opcao 8&gt;&#34;}
PUT  /api/v2/task/{task_id}                           {&#34;points&#34;: 8}
</code></pre><p>São quatro casos, e eu perdi tempo por não ter isso escrito em lugar nenhum. Criar task com ponto é uma chamada só. Atualizar o campo nativo de uma task existente é <code>PUT</code> com <code>points</code>. Atualizar o custom field é <a href="https://developer.clickup.com/reference/setcustomfieldvalue"><code>POST</code> no endpoint de custom field</a>. E manter os dois em sincronia numa task existente é obrigatoriamente duas chamadas.</p>
<p>Tem uma armadilha a mais no dropdown, e ela vale nos dois sentidos. Ao <strong>escrever</strong>, o campo não aceita o número 5, aceita o UUID da opção que vale 5. Ao <strong>ler</strong>, ele devolve <code>orderindex</code>, e a ordem configurada no meu board não é a ordem numérica: o índice 1 é o ponto 3 e o índice 2 é o ponto 2. Quem lê uma task e reescreve confiando no índice troca 3 por 2 sem receber erro nenhum. A única fonte confiável é o mapeamento entre o UUID canônico e o valor semântico, que é exatamente o que o <code>reference.md</code> existe pra guardar.</p>
<h2 id="o-default-8">O default 8</h2>
<p>Essa é a parte em que a IA quase me passou a perna sem querer.</p>
<p>Eu tinha um conjunto de subtasks pra pontuar, e o campo já vinha preenchido: todas com 8. Olhei e o número não cheirava bem, porque 8 é o teto da escala do board, que vai de 1 a 8 e não tem 13. Teto em tudo, do item mais trivial da lista, um fallback de valor padrão, até o mais pesado, um fluxo conversacional de várias etapas. Isso não é estimativa. É o padrão clássico de quem aplicou um valor em lote e seguiu a vida.</p>
<p>Se eu tivesse pedido pro Claude só copiar o que já estava lá, ele teria propagado aquilo com a maior cara de competência, e sem cometer erro nenhum de execução. Aqui mora a distinção que eu não sabia formular antes desse dia: <strong>transportar uma estimativa e produzir uma estimativa são operações diferentes</strong>. Transportar é mecânico e a máquina faz melhor que eu. Produzir exige comparar o item com outros que já foram entregues, e essa é justamente a evidência que falta pro modelo: ele lê o título, o escopo dito na conversa e o que apareceu no código durante a sessão, mas não tem o histórico de quanto doeu a última feature parecida.</p>
<p>Pedi pra reestimar item a item e o resultado saiu variado, de 2 a 8, somando bem menos que o bloco de oito em tudo. A regra que ficou pra skill é declarar contexto insuficiente em vez de arriscar quando o escopo não apareceu na conversa. A que ficou pra mim é mais curta: ferramenta nenhuma substitui o seu julgamento sobre o tamanho das coisas. Ela acelera o registro, não a decisão.</p>
<h2 id="o-que-pode-dar-errado-no-meio-da-escrita">O que pode dar errado no meio da escrita</h2>
<p>Volte na criação em duas etapas. O <code>POST</code> cria a task e já grava o custom field de ponto. Se o <code>PUT</code> seguinte falhar, por rede, por 429, por um <code>500</code> do outro lado, o script morre ali. E o que fica pra trás é isto: a task existe, o board mostra o ponto no campo do time, e o Sprint Points nativo está vazio. A velocity passa a divergir do número que aparece na tela.</p>
<p>Ou seja, o modo de falha da minha implementação produzia exatamente a inconsistência que ela existia pra evitar. E o pior detalhe é que ela falha em silêncio do ponto de vista do board: ninguém recebe erro, ninguém vê task pela metade, só um número que não bate daqui a uma semana.</p>
<p>A correção de fundo é a chamada única que a API já permitia, porque ela elimina a janela entre os dois estados. Onde a janela é inevitável, no caso de repontuar task existente, o certo é ler de volta e comparar antes de dizer que deu certo.</p>
<h2 id="o-fallback-que-troca-a-sua-identidade">O fallback que troca a sua identidade</h2>
<p>O token nunca esteve no repositório nem no arquivo da skill, e nisso eu não abri mão. Quando o próprio modelo sugeriu, em algum momento, salvar o token num arquivo pra facilitar, eu recusei. Credencial em texto puro versionada é dívida que volta pra te morder, e construir ferramenta nova não é desculpa pra repetir o erro que a gente passa a vida pedindo pros outros não cometerem.</p>
<p>O que eu não tinha percebido é mais sutil, e não é sobre onde o segredo mora. É sobre <strong>qual identidade ele carrega</strong>.</p>
<p>O script lê <code>$CLICKUP_TOKEN</code> do ambiente e, se não achar, cai num secret no AWS Secrets Manager. Em shell interativo, a variável aponta pro meu token pessoal. Em shell não interativo a coisa muda: o <code>~/.bashrc</code> tem o guard clássico que retorna cedo quando não há sessão interativa, o export nunca acontece, a variável chega vazia, e o script cai no fallback sem reclamar. O segredo que ele recupera pertence a uma conta de serviço, não a mim. A operação funciona, a task é criada, e a autoria registrada muda.</p>
<p>O problema não é o Secrets Manager, que é exatamente o lugar certo pra guardar isso. O problema é um fallback silencioso trocar o principal da operação. O que eu quero é que a origem da credencial apareça, que a identidade escolhida seja registrada antes da escrita, e que a execução falhe quando a identidade esperada não estiver disponível, em vez de trocar de principal por conta própria.</p>
<p>O engraçado é que a skill já faz isso certo em outro lugar. O comando que cria documento recusa o fallback e exige uma flag explícita pra usar a conta de serviço, porque ali a consequência é visível na hora, o documento nasce com dono errado. Na criação de task a consequência é invisível, e por isso a proteção não foi escrita. Consequência invisível é onde a gente esquece de se proteger.</p>
<h2 id="quem-aprovou-quem-executou-quem-assina">Quem aprovou, quem executou, quem assina</h2>
<p>O passo de confirmação antes de escrever em lote não é cosmético. Não é &ldquo;você tem certeza?&rdquo;, que ninguém lê depois da terceira vez. O que aparece na tela é o plano inteiro, com alvo, ponto e épico de cada item, e é a última chance de pegar a lista certa na sprint errada, que é o erro caro e chato de desfazer.</p>
<p>Junto veio a marcação de procedência: toda task aberta pela skill leva no fim da descrição um PS dizendo que foi aberta automaticamente pelo Claude Code. Ela tem dois defeitos que eu já enxergo. É prosa na descrição e não campo estruturado, então não dá pra filtrar nem contar por ela num relatório, e marcador de procedência devia ser campo num board que tem campos. E texto igual repetido em toda task vira ruído, porque o leitor para de enxergar depois da décima.</p>
<p>O que essa marcação registra, no fim, é só uma das três coisas que a gente costuma confundir numa frase só. <strong>Quem executou</strong> é o que o PS diz. <strong>Sob qual identidade</strong> é o que o token define, e é o que o fallback silencioso pode mentir. <strong>Quem respondeu pela decisão</strong> sou eu, sempre, porque fui eu que aprovei o plano. Automação que embaralha as três produz um board onde ninguém sabe a quem perguntar.</p>
<h2 id="o-loop-que-fechou-sozinho">O loop que fechou sozinho</h2>
<p>No fim daquela sessão, investigando algumas tasks, apareceu uma ação residual que não estava no board. Pedi pra registrar. A skill, agora pronta, criou a task. Foi a primeira vez que ela rodou a partir de uma decisão tomada na própria conversa, em vez de um comando meu explícito de &ldquo;cria task tal&rdquo;. Pequeno, mas foi o sinal de que a ferramenta tinha deixado de ser experimento e virado parte do fluxo.</p>
<h2 id="os-controles-que-eu-adicionaria-hoje">Os controles que eu adicionaria hoje</h2>
<p>Nada do que está aqui existe na implementação. Estou escrevendo como dívida, não como recurso.</p>
<p>Não há timeout explícito nas chamadas HTTP, então uma conexão pendurada trava a execução até o sistema operacional desistir. Não há retry nem backoff. O <code>429</code> não é tratado, cai no mesmo caminho de erro genérico de qualquer outro status. Não há idempotência nem deduplicação: rodar o mesmo lote duas vezes cria o dobro de tasks, e o único guarda-corpo é a minha atenção na hora de aprovar. Não há dry-run de verdade, e não há verificação depois da escrita.</p>
<p>Sobre o <code>429</code>, vale fazer a conta em vez de dar de ombros. O <a href="https://developer.clickup.com/docs/rate-limits">limite oficial</a> no plano Business é de 100 requisições por minuto por token, com os headers <code>X-RateLimit-Limit</code>, <code>X-RateLimit-Remaining</code> e <code>X-RateLimit-Reset</code> na resposta. Aquelas 15 tasks a duas chamadas dão cerca de 30 requisições, bem abaixo do teto. Só que o limite é <strong>por token</strong>, e esse mesmo token é usado pelo robô de custo que roda toda manhã, então as duas automações competem pela mesma cota. Um lote maior, ou dois ao mesmo tempo, chegam lá sem esforço.</p>
<p>O formato que eu quero é este:</p>
<pre tabindex="0"><code>plano aprovado
  -&gt; --dry-run: imprime o payload final de cada task e nao chama a API
  -&gt; escrita: um POST por task, com points e custom_fields no mesmo corpo
  -&gt; 429 ou 5xx: respeita X-RateLimit-Reset, tenta de novo, e desiste com o indice do lote
  -&gt; verificacao: GET /task/{id} e compara points e custom field com o plano
  -&gt; divergiu: reporta a task pelo id e diz qual campo ficou pra tras
</code></pre><p>O que essa sequência entrega não é robustez genérica. É a capacidade de responder uma pergunta específica depois de uma execução interrompida: quais itens do plano ficaram no board, quais não, e quais ficaram pela metade. Hoje a resposta é abrir o ClickUp e olhar.</p>
<h2 id="o-que-fica">O que fica</h2>
<p>O ganho de tempo é real. Construir a skill custou menos que documentar uma sprint cheia na mão, e ela paga esse custo toda vez que eu abro um board agora. Mas o que ficou mais valioso não foi ficar mais rápido, foi ficar mais <strong>correto</strong>: os dois campos de ponto sempre iguais, o épico sempre preenchido, a sprint sempre a corrente. Registro feito na mão às onze da noite erra mais que isso.</p>
<p>O resto é sobre a natureza da revisão. Quando uma IA só lê e responde, um erro dela é um parágrafo errado. Quando ela escreve num sistema que o time usa, um erro vira ruído no board, número torto na velocity e decisão tomada em cima de dado falso. E o erro que eu achei aqui não foi nem do modelo: foi meu, escondido atrás de uma sequência de chamadas que funcionava em todos os dias normais. Ferramenta que escreve no mundo externo merece a mesma revisão que código de produção, e o teste de que ela está pronta não é ela funcionar. É você saber dizer o que sobra quando ela falha no meio.</p>
]]></content:encoded><category>claude-code</category><category>ai</category><category>dev-tools</category><category>anthropic</category><category>produtividade</category></item><item><title>A API HTTP escondida do meu DVR Intelbras: ajustando 4 câmeras pela linha de comando</title><link>https://rifeli.dev/blog/2026-07-29-api-escondida-dvr-intelbras-mhdx-curl/</link><pubDate>Wed, 29 Jul 2026 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/2026-07-29-api-escondida-dvr-intelbras-mhdx-curl/</guid><description>Meu DVR Intelbras MHDX 3004-C expõe uma API CGI no estilo Dahua que ninguém documenta direito. Em vez de clicar canal por canal numa interface web pesada, li e ajustei a configuração das câmeras com curl e autenticação Digest. Conto o passo a passo real: como achei a porta, como autentiquei, como li as tabelas de config antes de escrever qualquer coisa, o que dá pra controlar (encoder, cor, redução de ruído) e o que eu não consegui controlar por ela (exposição e anti-flicker), e como mantive a senha fora do histórico.</description><content:encoded><![CDATA[<img id="image-custom" src="/images/posts/7c4e6bdc-21e4-4dde-8a66-73a75ee47222.png" alt="Quatro câmeras ligadas a um DVR, com o monitor em visão quádrupla à esquerda e respostas OK de uma API à direita" />
<p id="image-legend"></p>
<h2 id="introdução">Introdução</h2>
<p>Comecei a tarde de domingo querendo só arrumar uma câmera que estava com a imagem estranha e terminei com um script que lê e escreve a configuração das quatro câmeras do meu DVR direto pela linha de comando. No caminho descobri que o Intelbras MHDX 3004-C expõe uma interface CGI compatível com a família de APIs da Dahua, coisa que quase ninguém documenta em português. Faço a ressalva porque ela importa: eu não abri o firmware nem li strings de build pra saber de quem é o código. O que eu tenho é comportamento observado. A autenticação, os caminhos em <code>/cgi-bin/</code> e os nomes das tabelas seguem o mesmo padrão da Dahua, o que já é suficiente pra documentação de Dahua servir de mapa aqui. Origem do firmware é outra afirmação, bem mais forte, e essa eu não posso fazer.</p>
<p>A interface web do aparelho é aquela coisa pesada, cheia de combobox, em que você ajusta um parâmetro, troca de canal, espera recarregar, repete. Com quatro câmeras e um punhado de parâmetros pra padronizar, isso é trabalho manual chato e propenso a erro. A pergunta óbvia pra quem vive em terminal: dá pra fazer isso por API? Dá. E o caminho até lá é um bom exercício de engenharia reversa educada, do tipo ler antes de escrever.</p>
<p>Este post documenta o que funcionou de verdade no meu aparelho: MHDX 3004-C, firmware <code>4.001.00IB000</code>, build de agosto de 2024, com as quatro câmeras analógicas que estão ligadas nele. Fora desse conjunto exato eu não garanto nada. Nome de tabela, índice, faixa de valor, capability anunciada, resposta de erro e teto de FPS mudam entre modelo, revisão de hardware e versão de firmware. É exatamente por isso que a primeira metade do trabalho é descobrir, não chutar, e é por isso que não vale ler isso aqui como receita pra linha Intelbras inteira nem pra qualquer aparelho Dahua.</p>
<h2 id="achando-a-porta">Achando a porta</h2>
<p>Primeiro fato: o DVR estava na mesma rede que a máquina de onde eu trabalho. Peguei o IP local dele na própria tela de rede do aparelho (ele estava em DHCP, o que por si só já é um problema que comento no final). O teste inicial de alcance foi direto:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>ping -c2 <span style="color:#f1fa8c">&#34;</span><span style="color:#8be9fd;font-style:italic">$DVR_IP</span><span style="color:#f1fa8c">&#34;</span>
</span></span><span style="display:flex;"><span>curl -s -o /dev/null -w <span style="color:#f1fa8c">&#34;%{http_code}\n&#34;</span> <span style="color:#f1fa8c">&#34;http://</span><span style="color:#8be9fd;font-style:italic">$DVR_IP</span><span style="color:#f1fa8c">&#34;</span>
</span></span></code></pre></div><p>O ping não recebeu nenhuma resposta, e o <code>curl</code> devolveu <code>http_code=000</code>. Quase desisti achando que não alcançava o aparelho. Estava errado nas duas leituras.</p>
<p>Ping sem resposta em DVR é comum, muito aparelho simplesmente não responde ICMP. E <code>000</code> não é um código HTTP: é o <code>curl</code> avisando que não chegou a receber resposta HTTP nenhuma. Pode ser timeout, conexão recusada, falta de rota, protocolo errado, qualquer coisa que aconteça antes do servidor responder. Como diagnóstico, <code>000</code> sozinho não vale nada, ele só diz &ldquo;não deu&rdquo;.</p>
<p>O que vale é pedir pro <code>curl</code> contar o motivo. Do 7.75 em diante dá pra imprimir o exit code e a mensagem de erro no mesmo <code>-w</code>, e com <code>-sS</code> o erro para de ficar escondido:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>curl -sS --connect-timeout <span style="color:#bd93f9">3</span> <span style="color:#f1fa8c">\
</span></span></span><span style="display:flex;"><span>  -o /dev/null <span style="color:#f1fa8c">\
</span></span></span><span style="display:flex;"><span>  -w <span style="color:#f1fa8c">&#39;http=%{http_code} exit=%{exitcode} erro=%{errormsg}\n&#39;</span> <span style="color:#f1fa8c">\
</span></span></span><span style="display:flex;"><span>  <span style="color:#f1fa8c">&#34;http://</span><span style="color:#8be9fd;font-style:italic">$DVR_IP</span><span style="color:#f1fa8c">&#34;</span>
</span></span></code></pre></div><p>Aí o <code>000</code> deixa de ser beco sem saída e vira frase: exit 7 é conexão recusada ou sem rota, exit 28 é timeout, exit 6 é DNS. Em <code>curl</code> mais velho que 7.75 esses dois campos não existem, e o equivalente é olhar o <code>$?</code> do shell e deixar o stderr aparecer. Não guardei a saída da tentativa original, então não vou dizer aqui qual exit code apareceu naquele domingo.</p>
<p>A parte que eu descobri em seguida: a interface web não estava na 80. O aparelho usa uma porta HTTP customizada, alta. Com a porta certa:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>curl -sS -o /dev/null -w <span style="color:#f1fa8c">&#34;%{http_code}\n&#34;</span> <span style="color:#f1fa8c">&#34;http://</span><span style="color:#8be9fd;font-style:italic">$DVR_IP</span><span style="color:#f1fa8c">:</span><span style="color:#8be9fd;font-style:italic">$PORT</span><span style="color:#f1fa8c">&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># 200</span>
</span></span></code></pre></div><p>Lição que sempre esqueço: separe &ldquo;o host está vivo&rdquo; de &ldquo;o serviço está na porta que eu chutei&rdquo;. Ping testa resposta a ICMP e nada mais, não diz se tem HTTP escutando em algum lugar. São perguntas diferentes, e eu tinha feito a errada duas vezes seguidas.</p>
<h2 id="a-autenticação-é-digest-não-basic">A autenticação é Digest, não Basic</h2>
<p>Com a web respondendo, fui bater na API CGI. O padrão Dahua expõe tudo em <code>/cgi-bin/</code>. O primeiro probe é pedir o tipo do aparelho:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>curl -sS -i <span style="color:#f1fa8c">&#34;http://</span><span style="color:#8be9fd;font-style:italic">$DVR_IP</span><span style="color:#f1fa8c">:</span><span style="color:#8be9fd;font-style:italic">$PORT</span><span style="color:#f1fa8c">/cgi-bin/magicBox.cgi?action=getDeviceType&#34;</span>
</span></span></code></pre></div><p>A resposta foi um <code>401 Unauthorized</code> com um header revelador:</p>
<pre tabindex="0"><code>WWW-Authenticate: Digest realm=&#34;Login to ...&#34;, qop=&#34;auth&#34;, nonce=&#34;...&#34;, opaque=&#34;&#34;
</code></pre><p><code>Digest</code>, não <code>Basic</code>. Isso muda o <code>curl</code>: tem que usar <code>--digest</code>, senão a credencial nem é negociada direito. O <code>curl</code> faz o desafio-resposta de Digest sozinho, então na prática é só trocar a flag:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>curl -sS --digest -u <span style="color:#f1fa8c">&#34;</span><span style="color:#8be9fd;font-style:italic">$DVR_USER</span><span style="color:#f1fa8c">:</span><span style="color:#8be9fd;font-style:italic">$DVR_PASS</span><span style="color:#f1fa8c">&#34;</span> <span style="color:#f1fa8c">\
</span></span></span><span style="display:flex;"><span>  <span style="color:#f1fa8c">&#34;http://</span><span style="color:#8be9fd;font-style:italic">$DVR_IP</span><span style="color:#f1fa8c">:</span><span style="color:#8be9fd;font-style:italic">$PORT</span><span style="color:#f1fa8c">/cgi-bin/magicBox.cgi?action=getDeviceType&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># type=MHDX 3004-C</span>
</span></span></code></pre></div><p>Funcionou. A partir daqui é tudo <code>configManager.cgi</code>.</p>
<p>Uma ressalva que precisa vir junto: Digest é melhor que Basic porque a senha não viaja em claro, ele faz desafio-resposta com hash em cima do nonce. Mas isso autentica, não cifra. A conexão continua sendo <code>http://</code>, então URL, endpoint, parâmetros que eu escrevo e a config que volta na resposta ficam todos legíveis pra quem estiver na mesma rede olhando o tráfego. Por isso isso aqui é coisa de rede local controlada, de preferência num segmento isolado só pros bichos de câmera, e por isso DVR não fica publicado na internet. Se o firmware do teu oferecer HTTPS de forma confiável, prefira HTTPS.</p>
<h2 id="mantendo-a-senha-fora-do-histórico">Mantendo a senha fora do histórico</h2>
<p>Antes de seguir, um parêntese que pra mim não é negociável. Senha de DVR é credencial de sistema de segurança física da minha casa. Ela não vai pro histórico do shell, não vai pro corpo de um script versionado, e não vai num post público. O padrão que usei foi um arquivo de credenciais com permissão restrita, que os scripts apenas carregam:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">umask</span> <span style="color:#bd93f9">077</span>
</span></span><span style="display:flex;"><span>cat &gt; ~/.dvr_creds <span style="color:#f1fa8c">&lt;&lt;&#39;EOF&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">DVR_USER=&#39;admin&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">DVR_PASS=&#39;troque aqui&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">EOF</span>
</span></span><span style="display:flex;"><span>chmod <span style="color:#bd93f9">600</span> ~/.dvr_creds
</span></span></code></pre></div><p>As aspas simples não são decoração. Esse arquivo vai ser lido com <code>source</code>, ou seja, é shell de verdade: senha com espaço, <code>$</code>, crase ou aspas duplas sem quoting vira expansão ou erro de sintaxe, e você descobre isso na forma de um 401 que não faz sentido. Aspas simples resolvem tudo menos a própria aspa simples; se a tua senha tiver uma, escapa com <code>'\''</code> ou troca a senha.</p>
<p>E todo script começa com:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">source</span> ~/.dvr_creds
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">AUTH</span><span style="color:#ff79c6">=(</span>--digest -u <span style="color:#f1fa8c">&#34;</span><span style="color:#f1fa8c">${</span><span style="color:#8be9fd;font-style:italic">DVR_USER</span><span style="color:#f1fa8c">}</span><span style="color:#f1fa8c">:</span><span style="color:#f1fa8c">${</span><span style="color:#8be9fd;font-style:italic">DVR_PASS</span><span style="color:#f1fa8c">}</span><span style="color:#f1fa8c">&#34;</span><span style="color:#ff79c6">)</span>
</span></span></code></pre></div><p>O array ajuda em organização e quoting, mantém a credencial como um argumento só e evita que ela se espalhe pela linha em cada chamada. Mas vale ser honesto sobre o que ele não faz: no instante em que o <code>curl</code> roda, os argumentos já foram expandidos pro processo, e a senha está na linha de comando visível em <code>ps</code> e em <code>/proc</code>. Array nenhum conserta isso. Pra rede doméstica controlada eu aceito essa troca. Em máquina compartilhada eu não aceitaria, e o caminho seria <code>--netrc</code> ou um cofre de verdade. Fica registrado que eu não validei o <code>--netrc</code> contra esse DVR, então não estou recomendando como testado, e vale saber que a entrada do <code>.netrc</code> casa por host, não por porta, o que importa justamente aqui porque o aparelho não está na 80.</p>
<h2 id="ler-antes-de-escrever">Ler antes de escrever</h2>
<p>Aqui está o princípio que separa mexer com confiança de quebrar o sistema às cegas: <strong>a primeira coisa que a API faz é leitura, não escrita</strong>. Os nomes dos parâmetros variam entre versões de firmware, e eu não ia descobrir o nome certo escrevendo e torcendo. O <code>configManager.cgi</code> lê qualquer tabela com <code>action=getConfig&amp;name=&lt;Tabela&gt;</code>.</p>
<p>A configuração de codificação fica em <code>Encode</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>curl -s <span style="color:#f1fa8c">&#34;</span><span style="color:#f1fa8c">${</span><span style="color:#8be9fd;font-style:italic">AUTH</span>[@]<span style="color:#f1fa8c">}</span><span style="color:#f1fa8c">&#34;</span> <span style="color:#f1fa8c">\
</span></span></span><span style="display:flex;"><span>  <span style="color:#f1fa8c">&#34;http://</span><span style="color:#8be9fd;font-style:italic">$DVR_IP</span><span style="color:#f1fa8c">:</span><span style="color:#8be9fd;font-style:italic">$PORT</span><span style="color:#f1fa8c">/cgi-bin/configManager.cgi?action=getConfig&amp;name=Encode&#34;</span> <span style="color:#f1fa8c">\
</span></span></span><span style="display:flex;"><span>  | grep -E <span style="color:#f1fa8c">&#39;^table\.Encode\[0\]&#39;</span>
</span></span></code></pre></div><p>A saída é um formato chave-valor plano, com índices entre colchetes. O canal 0 da API é o canal 1 do aparelho. Cada canal tem <code>MainFormat[0]</code> (o stream principal, que grava) e <code>ExtraFormat[0]</code> (o stream extra, leve, pra ver remoto):</p>
<pre tabindex="0"><code>table.Encode[0].MainFormat[0].Video.Compression=H.265
table.Encode[0].MainFormat[0].Video.resolution=1920x1080
table.Encode[0].MainFormat[0].Video.FPS=15
table.Encode[0].MainFormat[0].Video.BitRateControl=VBR
table.Encode[0].MainFormat[0].Video.BitRate=3072
table.Encode[0].MainFormat[0].Video.Quality=6
table.Encode[0].MainFormat[0].AudioEnable=false
table.Encode[0].ExtraFormat[0].Video.resolution=704x480
table.Encode[0].ExtraFormat[0].Video.FPS=10
table.Encode[0].ExtraFormat[0].Video.BitRate=512
</code></pre><p>Esse dump é o mapa. A partir dele eu sei exatamente o nome de cada campo que vou querer escrever, sem adivinhação.</p>
<h2 id="as-tabelas-que-importam">As tabelas que importam</h2>
<p>Lendo as tabelas relevantes, montei o mapeamento entre o que a interface gráfica chama de uma coisa e o que a API chama de outra. Esse foi o pulo do gato, porque os nomes não batem:</p>
<p>A tabela <code>VideoColor</code> guarda brilho, contraste, saturação, matiz e, surpresa, a nitidez (que a UI chama de &ldquo;Nitidez&rdquo; e a API chama de <code>Acutance</code>). Ela é indexada por canal e por período de tempo, <code>VideoColor[canal][periodo]</code>. O período <code>[0]</code> é o perfil ativo (seção de tempo habilitada, <code>00:00-24:00</code>); o <code>[1]</code> é uma segunda faixa que no meu caso estava desabilitada. Ou seja, eu só escrevo no período <code>[0]</code>:</p>
<pre tabindex="0"><code>table.VideoColor[0][0].Brightness=50
table.VideoColor[0][0].Contrast=50
table.VideoColor[0][0].Saturation=50
table.VideoColor[0][0].Hue=50
table.VideoColor[0][0].Acutance=5
table.VideoColor[0][0].TimeSection=1 00:00:00-24:00:00
</code></pre><p>Ler <code>VideoColor</code> de todos os canais de uma vez também serviu pra confirmar o mapeamento de índice, batendo brilho e contraste com o que eu via na tela canal a canal. Conferir o mapa contra a realidade antes de escrever evitou que eu sobrescrevesse o canal errado.</p>
<p>E aqui um dado me corrigiu. Minha ideia inicial era padronizar tudo em 50, ponto neutro de uma escala 0-100. Funciona pra brilho, contraste e saturação. Mas a leitura mostrou que a <code>Acutance</code> (nitidez) das câmeras estava em 1 a 5, e a câmera que eu achava a melhor de todas rodava em 1. Numa escala 0-100, esses valores são quase nada de realce, e por bom motivo: nitidez alta à noite só amplifica grão do infravermelho. Se eu tivesse cravado 50 no automático, teria deixado as quatro super-realçadas e cheias de ruído. Padronizei em 5. A lição vale além do DVR: &ldquo;valor neutro&rdquo; presumido não é &ldquo;valor bom&rdquo;, e só a leitura do que o aparelho já usava me mostrou a diferença.</p>
<p>A &ldquo;Redução de ruído&rdquo; da interface não está em <code>VideoColor</code> nem no <code>VideoInOptions</code>. Demorei a achar: ela vive em <code>VideoInDenoise</code>, com controle 2D e 3D separados:</p>
<pre tabindex="0"><code>table.VideoInDenoise[0][0].2DEnable=true
table.VideoInDenoise[0][0].2DLevel=100
table.VideoInDenoise[0][0].3DAutoType.AutoLevel=40
</code></pre><p>E tem um campo de ganho de imagem em <code>VideoInOptions[canal].Gain</code>, com uma variante <code>NightOptions.Gain</code> pro modo noturno. Esse eu não decifrei. O valor do <code>Gain</code> na API não bate número a número com o slider &ldquo;Ganho imagem&rdquo; da UI, e mais pra frente o <code>getCaps</code> do canal ainda me devolveu <code>caps.Gain=false</code>. Um campo que existe na tabela de config e uma capability negada pelo canal aparentemente não são a mesma coisa, provavelmente camadas diferentes, processamento no gravador de um lado e ganho nativo da câmera do outro, ou um parâmetro legado que ficou na tabela. Não fechei isso com o que o aparelho me devolveu, então fica registrado como não esclarecido em vez de explicado por chute. Na prática, mais um motivo pra testar num canal e conferir no preview antes de replicar.</p>
<h2 id="o-que-a-api-não-deixa-fazer">O que a API NÃO deixa fazer</h2>
<p>Aqui vem a parte honesta, e a mais importante de um post de engenharia reversa: nem tudo está exposto. A câmera que eu queria consertar tinha listras horizontais, o clássico flicker de iluminação, em que uma lâmpada LED pulsando bate com o obturador eletrônico da câmera. O ajuste pra isso é o anti-flicker, que vive na exposição. Fui ler a tabela de exposição:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>curl -s <span style="color:#f1fa8c">&#34;</span><span style="color:#f1fa8c">${</span><span style="color:#8be9fd;font-style:italic">AUTH</span>[@]<span style="color:#f1fa8c">}</span><span style="color:#f1fa8c">&#34;</span> <span style="color:#f1fa8c">\
</span></span></span><span style="display:flex;"><span>  <span style="color:#f1fa8c">&#34;http://</span><span style="color:#8be9fd;font-style:italic">$DVR_IP</span><span style="color:#f1fa8c">:</span><span style="color:#8be9fd;font-style:italic">$PORT</span><span style="color:#f1fa8c">/cgi-bin/configManager.cgi?action=getConfig&amp;name=VideoInExposure&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># Error</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># Bad Request!</span>
</span></span></code></pre></div><p><code>Bad Request</code>. O que isso me autoriza a dizer é modesto: o firmware não aceitou <code>VideoInExposure</code> por esse endpoint e nesse formato. <code>Bad Request</code> cobre várias coisas, nome de tabela diferente, formato de requisição inválido, permissão do usuário, endpoint que não é esse. Não é prova de que a tabela não existe. Varri os nomes prováveis (<code>VideoInAntiFlicker</code>, <code>VideoInDayNight</code>, <code>VideoInNR</code>) e todos voltaram vazios, o que aumenta a suspeita mas continua sendo ausência de resposta útil, não demonstração de ausência.</p>
<p>Pra não parar no &ldquo;não achei a tabela&rdquo;, fui perguntar ao próprio aparelho o que ele suporta naquele canal, com <code>devVideoInput.cgi?action=getCaps</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>curl -g -s <span style="color:#f1fa8c">&#34;</span><span style="color:#f1fa8c">${</span><span style="color:#8be9fd;font-style:italic">AUTH</span>[@]<span style="color:#f1fa8c">}</span><span style="color:#f1fa8c">&#34;</span> <span style="color:#f1fa8c">\
</span></span></span><span style="display:flex;"><span>  <span style="color:#f1fa8c">&#34;http://</span><span style="color:#8be9fd;font-style:italic">$DVR_IP</span><span style="color:#f1fa8c">:</span><span style="color:#8be9fd;font-style:italic">$PORT</span><span style="color:#f1fa8c">/cgi-bin/devVideoInput.cgi?action=getCaps&amp;channel=2&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># caps.Gain=false</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># caps.VideoInDenoise.2D.Support=true</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># caps.ImageEnhancement.Support=true</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># ... nenhuma linha de shutter, exposure, antiflicker ou WDR</span>
</span></span></code></pre></div><p>Isso é mais forte que o <code>Bad Request</code>, mas ainda não é onisciência. O que os dois juntos sustentam: as tabelas que eu procurei não foram aceitas, e o canal não anunciou capability de shutter, exposição, antiflicker ou WDR nos endpoints que eu testei. Ou seja, <strong>com os endpoints e as capabilities expostos por esse conjunto de DVR, firmware e câmera, eu não encontrei um caminho suportado pra controlar o anti-flicker</strong>. Isso não é a mesma coisa que dizer que o controle não existe em nenhum canto da API, nem que a câmera não tem esse ajuste por outro caminho, nem que outro firmware não exporia. Não testei tudo, e não tenho como testar tudo.</p>
<p>O palpite informado, e eu marco que é palpite, é que são câmeras analógicas HDCVI simples, de exposição automática. Nessa família, quando o ajuste existe, costuma morar no menu OSD da câmera, navegado por sinal coaxial, e não na config do gravador. O <code>coaxialControlIO.cgi</code> respondeu (consegui ler status de speaker e luz branca), então o canal de controle pra câmera existe. Só não achei do outro lado dele o botão que eu queria.</p>
<p>Saber o limite da ferramenta é tão valioso quanto saber o que ela faz. Eu poderia ter perdido uma hora montando um <code>setConfig</code> de anti-flicker que o aparelho ia ignorar em silêncio, ou pior, subido numa escada pra caçar um menu OSD que talvez não esteja lá. Duas leituras de API me pouparam as duas coisas em segundos.</p>
<h2 id="backup-antes-da-primeira-escrita">Backup antes da primeira escrita</h2>
<p>Regra que eu não quebro nem em casa: antes do primeiro <code>setConfig</code>, tira um retrato do que está lá. Não porque eu espero errar, mas porque a config atual é a única referência do que era o estado bom, e esse aparelho não tem desfazer.</p>
<p>Aqui também muda a natureza dos comandos. Até agora eu estava explorando na mão, e podia ler a saída de cada um. Daqui pra frente é automação, e automação precisa de timeout e de erro visível:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">source</span> ~/.dvr_creds
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">CURL</span><span style="color:#ff79c6">=(</span>-sS -g --digest -u <span style="color:#f1fa8c">&#34;</span><span style="color:#f1fa8c">${</span><span style="color:#8be9fd;font-style:italic">DVR_USER</span><span style="color:#f1fa8c">}</span><span style="color:#f1fa8c">:</span><span style="color:#f1fa8c">${</span><span style="color:#8be9fd;font-style:italic">DVR_PASS</span><span style="color:#f1fa8c">}</span><span style="color:#f1fa8c">&#34;</span>
</span></span><span style="display:flex;"><span>      --connect-timeout <span style="color:#bd93f9">3</span> --max-time 15<span style="color:#ff79c6">)</span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">BASE</span><span style="color:#ff79c6">=</span><span style="color:#f1fa8c">&#34;http://</span><span style="color:#8be9fd;font-style:italic">$DVR_IP</span><span style="color:#f1fa8c">:</span><span style="color:#8be9fd;font-style:italic">$PORT</span><span style="color:#f1fa8c">/cgi-bin&#34;</span>
</span></span></code></pre></div><p>O <code>-sS</code> cala a barra de progresso mas deixa o erro aparecer, o <code>--connect-timeout</code> e o <code>--max-time</code> evitam que um script fique pendurado a madrugada inteira num aparelho que travou. Do <code>curl</code> 7.76 em diante existe também o <code>--fail-with-body</code>, que faz o exit code refletir status HTTP de erro sem jogar o corpo da resposta fora. Só não bota ele no probe de descoberta que espera justamente um <code>401</code> pra revelar o esquema Digest, senão você transforma o resultado desejado em falha.</p>
<p>Com isso, o backup:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">BKP</span><span style="color:#ff79c6">=</span><span style="color:#f1fa8c">&#34;</span><span style="color:#8be9fd;font-style:italic">$HOME</span><span style="color:#f1fa8c">/dvr-backup-</span><span style="color:#ff79c6">$(</span>date +%Y%m%d-%H%M%S<span style="color:#ff79c6">)</span><span style="color:#f1fa8c">&#34;</span>
</span></span><span style="display:flex;"><span>mkdir -p <span style="color:#f1fa8c">&#34;</span><span style="color:#8be9fd;font-style:italic">$BKP</span><span style="color:#f1fa8c">&#34;</span> <span style="color:#ff79c6">&amp;&amp;</span> chmod <span style="color:#bd93f9">700</span> <span style="color:#f1fa8c">&#34;</span><span style="color:#8be9fd;font-style:italic">$BKP</span><span style="color:#f1fa8c">&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">for</span> t in VideoColor VideoInDenoise VideoInOptions Encode; <span style="color:#ff79c6">do</span>
</span></span><span style="display:flex;"><span>  curl <span style="color:#f1fa8c">&#34;</span><span style="color:#f1fa8c">${</span><span style="color:#8be9fd;font-style:italic">CURL</span>[@]<span style="color:#f1fa8c">}</span><span style="color:#f1fa8c">&#34;</span> <span style="color:#f1fa8c">&#34;</span><span style="color:#8be9fd;font-style:italic">$BASE</span><span style="color:#f1fa8c">/configManager.cgi?action=getConfig&amp;name=</span><span style="color:#8be9fd;font-style:italic">$t</span><span style="color:#f1fa8c">&#34;</span> <span style="color:#f1fa8c">\
</span></span></span><span style="display:flex;"><span>    &gt; <span style="color:#f1fa8c">&#34;</span><span style="color:#8be9fd;font-style:italic">$BKP</span><span style="color:#f1fa8c">/</span><span style="color:#8be9fd;font-style:italic">$t</span><span style="color:#f1fa8c">.txt&#34;</span> <span style="color:#ff79c6">||</span> <span style="color:#8be9fd;font-style:italic">echo</span> <span style="color:#f1fa8c">&#34;FALHOU: </span><span style="color:#8be9fd;font-style:italic">$t</span><span style="color:#f1fa8c">&#34;</span> &gt;&amp;<span style="color:#bd93f9">2</span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">done</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">{</span>
</span></span><span style="display:flex;"><span>  date -Iseconds
</span></span><span style="display:flex;"><span>  curl <span style="color:#f1fa8c">&#34;</span><span style="color:#f1fa8c">${</span><span style="color:#8be9fd;font-style:italic">CURL</span>[@]<span style="color:#f1fa8c">}</span><span style="color:#f1fa8c">&#34;</span> <span style="color:#f1fa8c">&#34;</span><span style="color:#8be9fd;font-style:italic">$BASE</span><span style="color:#f1fa8c">/magicBox.cgi?action=getDeviceType&#34;</span>
</span></span><span style="display:flex;"><span>  curl <span style="color:#f1fa8c">&#34;</span><span style="color:#f1fa8c">${</span><span style="color:#8be9fd;font-style:italic">CURL</span>[@]<span style="color:#f1fa8c">}</span><span style="color:#f1fa8c">&#34;</span> <span style="color:#f1fa8c">&#34;</span><span style="color:#8be9fd;font-style:italic">$BASE</span><span style="color:#f1fa8c">/magicBox.cgi?action=getSoftwareVersion&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">}</span> &gt; <span style="color:#f1fa8c">&#34;</span><span style="color:#8be9fd;font-style:italic">$BKP</span><span style="color:#f1fa8c">/contexto.txt&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>chmod <span style="color:#bd93f9">600</span> <span style="color:#f1fa8c">&#34;</span><span style="color:#8be9fd;font-style:italic">$BKP</span><span style="color:#f1fa8c">&#34;</span>/*
</span></span></code></pre></div><p>Modelo, versão de firmware e data no mesmo diretório das tabelas, porque dump de config sem saber de que firmware ele veio é quase inútil daqui a um ano. E não existe, pelo menos não que eu tenha achado nesse firmware, endpoint de restore em massa que engula esse arquivo de volta. Então trata o dump pelo que ele é: referência recuperável pra reaplicar valor por valor com <code>setConfig</code> se algo sair errado. Ele também é a configuração do sistema de segurança da tua casa, então mora com a mesma disciplina do arquivo de credencial, permissão fechada e longe de qualquer repositório.</p>
<h2 id="escrevendo-a-configuração-e-a-pegadinha-do-glob">Escrevendo a configuração (e a pegadinha do glob)</h2>
<p>Com o mapa na mão, escrever deveria ser trivial. O <code>setConfig</code> aceita múltiplos campos numa chamada, separados por <code>&amp;</code>. Montei a primeira escrita, mandei, e o servidor respondeu com uma linha em branco. Sem <code>OK</code>, sem erro. E a leitura de volta mostrava os valores antigos, intocados.</p>
<p>O motivo me custou alguns minutos: o <code>curl</code> interpreta <code>[</code> e <code>]</code> como <strong>globbing de URL</strong> (aquele recurso que expande <code>http://site/arq[1-10].jpg</code>). Os índices <code>[0][0]</code> dos nomes de parâmetro caíam direto nessa armadilha e a requisição saía mangled. A correção é uma flag, <code>-g</code> (ou <code>--globoff</code>):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>curl -g -s <span style="color:#f1fa8c">&#34;</span><span style="color:#f1fa8c">${</span><span style="color:#8be9fd;font-style:italic">AUTH</span>[@]<span style="color:#f1fa8c">}</span><span style="color:#f1fa8c">&#34;</span> <span style="color:#f1fa8c">\
</span></span></span><span style="display:flex;"><span>  <span style="color:#f1fa8c">&#34;http://</span><span style="color:#8be9fd;font-style:italic">$DVR_IP</span><span style="color:#f1fa8c">:</span><span style="color:#8be9fd;font-style:italic">$PORT</span><span style="color:#f1fa8c">/cgi-bin/configManager.cgi?action=setConfig\
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">&amp;VideoColor[0][0].Brightness=50\
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">&amp;VideoColor[0][0].Contrast=50\
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">&amp;VideoColor[0][0].Saturation=50\
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">&amp;VideoColor[0][0].Acutance=5&#34;</span>
</span></span></code></pre></div><p>Com o <code>-g</code>, a resposta vira <code>OK</code> e os valores pegam.</p>
<p>Antes de seguir, o aviso que falta nessa linha: <code>-g</code> resolve o colchete, não resolve encoding. Os valores que eu escrevo aqui são números, então passam limpos. No dia em que o valor tiver espaço ou caractere reservado, e tem, o <code>TimeSection</code> é literalmente <code>1 00:00:00-24:00:00</code>, ele precisa ir codificado. Pensei em montar tudo com <code>--get --data-urlencode</code>, que seria o jeito canônico, e não fui: nessa forma o <code>curl</code> codifica também o nome do parâmetro, e <code>VideoColor%5B0%5D%5B0%5D.Brightness</code> pode ou não ser aceito por esse CGI. Como eu não testei, fico com o <code>-g</code> que eu vi funcionando e digo o que não sei, em vez de recomendar por elegância. Mesma coisa pra POST: não confirmei que esse CGI aceita, então não converti.</p>
<p>Detalhe que vale ouro: foi a <strong>leitura de volta</strong> que denunciou a falha silenciosa. Se eu tivesse confiado no <code>OK</code> ausente sem reler, teria saído achando que apliquei quando não apliquei nada. O método pra padronizar as quatro câmeras vira um laço sobre os índices de canal, aplicando o mesmo conjunto de valores. Mas a disciplina aqui é a mesma de qualquer mudança em produção: <strong>aplica em um canal, confere no preview ao vivo, e só então replica pros outros</strong>. Num sistema de segurança eu não faço fan-out de uma mudança não verificada.</p>
<p>E o aparelho me deu mais um motivo pra reler tudo: ao padronizar o stream extra em 10fps, o <code>setConfig</code> respondeu <code>OK</code> nos quatro canais, mas a leitura de volta mostrou três deles travados em 7fps. O DVR aceitou o comando e <strong>clampou o valor em silêncio</strong> num teto de hardware do stream secundário. Nesse firmware, <code>OK</code> confirma que a requisição foi aceita, não que cada valor foi persistido exatamente como pedido. A única fonte de verdade é reler a config depois de escrever e comparar campo a campo. O que é uma coisa boba de automatizar, e que eu devia ter feito desde o começo:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>check<span style="color:#ff79c6">()</span> <span style="color:#ff79c6">{</span> <span style="color:#6272a4"># check &lt;tabela&gt; &lt;chave&gt; &lt;esperado&gt;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#8be9fd;font-style:italic">local</span> got
</span></span><span style="display:flex;"><span>  <span style="color:#8be9fd;font-style:italic">got</span><span style="color:#ff79c6">=</span><span style="color:#ff79c6">$(</span>curl <span style="color:#f1fa8c">&#34;</span><span style="color:#f1fa8c">${</span><span style="color:#8be9fd;font-style:italic">CURL</span>[@]<span style="color:#f1fa8c">}</span><span style="color:#f1fa8c">&#34;</span> <span style="color:#f1fa8c">&#34;</span><span style="color:#8be9fd;font-style:italic">$BASE</span><span style="color:#f1fa8c">/configManager.cgi?action=getConfig&amp;name=</span><span style="color:#8be9fd;font-style:italic">$1</span><span style="color:#f1fa8c">&#34;</span> <span style="color:#f1fa8c">\
</span></span></span><span style="display:flex;"><span>        | grep -F <span style="color:#f1fa8c">&#34;table.</span><span style="color:#8be9fd;font-style:italic">$2</span><span style="color:#f1fa8c">=&#34;</span> | cut -d<span style="color:#ff79c6">=</span> -f2-<span style="color:#ff79c6">)</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">if</span> <span style="color:#ff79c6">[</span> <span style="color:#f1fa8c">&#34;</span><span style="color:#8be9fd;font-style:italic">$got</span><span style="color:#f1fa8c">&#34;</span> <span style="color:#ff79c6">=</span> <span style="color:#f1fa8c">&#34;</span><span style="color:#8be9fd;font-style:italic">$3</span><span style="color:#f1fa8c">&#34;</span> <span style="color:#ff79c6">]</span>; <span style="color:#ff79c6">then</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">echo</span> <span style="color:#f1fa8c">&#34;ok        </span><span style="color:#8be9fd;font-style:italic">$2</span><span style="color:#f1fa8c">=</span><span style="color:#8be9fd;font-style:italic">$got</span><span style="color:#f1fa8c">&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">else</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">echo</span> <span style="color:#f1fa8c">&#34;DIVERGIU  </span><span style="color:#8be9fd;font-style:italic">$2</span><span style="color:#f1fa8c">: pedi </span><span style="color:#8be9fd;font-style:italic">$3</span><span style="color:#f1fa8c">, gravou </span><span style="color:#f1fa8c">${</span><span style="color:#8be9fd;font-style:italic">got</span><span style="color:#ff79c6">:-</span>&lt;vazio&gt;<span style="color:#f1fa8c">}</span><span style="color:#f1fa8c">&#34;</span> &gt;&amp;<span style="color:#bd93f9">2</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">return</span> <span style="color:#bd93f9">1</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">fi</span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>check Encode <span style="color:#f1fa8c">&#39;Encode[0].ExtraFormat[0].Video.FPS&#39;</span> <span style="color:#bd93f9">10</span>
</span></span></code></pre></div><p>Com isso o 7fps aparece como divergência na cara, não como um <code>OK</code> que eu li rápido. O ciclo que eu sigo agora é sempre esse: escreve, relê, compara pedido com persistido, e só então replica pros outros canais. É o tipo de detalhe que separa &ldquo;achei que configurei&rdquo; de &ldquo;configurei&rdquo;.</p>
<h2 id="o-que-levo-desse-domingo">O que levo desse domingo</h2>
<p>Três coisas ficam.</p>
<p>A primeira é que muito equipamento doméstico de prateleira tem uma API perfeitamente utilizável, e não documentada, atrás de uma interface ruim. Não que o fabricante esteja escondendo: é o mesmo endpoint que a interface web dele consome, só sem manual em português. DVR, roteador, nobreak, impressora. Vale o probe.</p>
<p>A segunda é o método: alcance, autenticação, leitura, mapeamento, e só no fim a escrita. A leitura não é só reconhecimento, é o que transforma chute em certeza e revela o que o aparelho não expõe.</p>
<p>A terceira é uma pendência que esse exercício escancarou: o DVR está em DHCP. Toda essa automação aponta pra um IP que pode mudar quando o roteador resolver. Próximo passo é reservar um IP fixo pra ele no DHCP do roteador, pra que o script não quebre sozinho numa madrugada qualquer. Endereço de infraestrutura que você automatiza não pode ser volátil, e isso vale tanto pra um DVR de casa quanto pra qualquer serviço que a gente opera em produção.</p>
<p>No fim das contas, a câmera com listra não tem conserto por API, pelo menos não por um caminho que eu tenha achado. A luz que causa o flicker é de um poste da rua, apagar não é opção. O conserto de software seria o anti-flicker, que sincroniza o tempo de exposição com a frequência da rede ou com a pulsação efetiva da iluminação. Numa rede de 60Hz o LED pode piscar em 60 ou em 120Hz dependendo do driver dele, e é por isso que não dá pra recomendar número mágico de shutter sem conhecer a câmera, a luz e os modos que o firmware oferece. De todo jeito é conversa teórica aqui: nem o <code>getConfig</code> das tabelas de exposição nem o <code>getCaps</code> do canal me deram um caminho suportado pra chegar nesse ajuste. Então sobra o físico: tirar o poste do enquadramento direto e pôr um capuz na lente pra cortar a luz que entra reta no sensor. Anticlimático, mas é a verdade que a investigação entregou, e descobri isso sem subir escada nenhuma. Saí com as quatro câmeras padronizadas em segundos em vez de meia hora de clique, e com um mapa da API que vou reusar toda vez que precisar mexer nelas.</p>
]]></content:encoded><category>intelbras</category><category>dahua</category><category>dvr</category><category>api</category><category>curl</category><category>homelab</category><category>engenharia-reversa</category></item><item><title>As oito famílias de matemática que rodam por trás da Plataforma Harmo todo dia</title><link>https://rifeli.dev/blog/2026-07-27-formulas-essenciais-data-science-harmo/</link><pubDate>Mon, 27 Jul 2026 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/2026-07-27-formulas-essenciais-data-science-harmo/</guid><description>Continuação do post sobre o alpinista no nevoeiro. Existe um cheatsheet clássico de fórmulas essenciais de data science: estatística descritiva, probabilidade, álgebra linear, cálculo, machine learning, information theory, data science essentials e séries temporais. Cada uma dessas oito famílias tem aplicação direta na operação da Plataforma Harmo, todo dia. Esse post mapeia onde cada uma mora.</description><content:encoded><![CDATA[<img id="image-custom" src="/images/posts/e7834d8b-c618-4db0-a1be-d685e1c8ad67.png" alt="Oito quadros de fórmulas, de estatística a otimização, com feixes de luz descendo até marcadores de lojas num mapa" />
<p id="image-legend">As oito famílias do cheatsheet não param na lousa: descem até a loja física e viram fluxo de gente na porta.</p>
<h2 id="introdução">Introdução</h2>
<p>Existe um cheatsheet clássico circulando pela internet com oito famílias de fórmulas essenciais de data science: estatística descritiva, probabilidade, álgebra linear, cálculo, machine learning, information theory, data science essentials e séries temporais. Na imagem, cada caixa traz três ou quatro fórmulas em notação acadêmica limpa. A reação mais comum de quem bate os olhos é &ldquo;isso é coisa de pesquisador, eu só faço CRUD&rdquo;.</p>
<p>Esse post amplia o que comecei a contar no <a href="/blog/2026-06-19-alpinista-nevoeiro-gradiente-descendente-ia/">post do alpinista no nevoeiro</a>. Lá mostrei que uma única ferramenta de Cálculo II, o gradiente descendente, sustenta o treinamento de grande parte da IA moderna. Aqui o quadro é maior. Cada uma das oito famílias do cheatsheet tem aplicação direta na operação da Plataforma Harmo, em volume que dá pra dimensionar: processamos 10 milhões de pesquisas e 300 mil avaliações públicas por mês, em mais de 60 mil lojas físicas. Nada disso roda sem matemática trabalhando no background, mesmo quando o time que opera não chama as coisas por esses nomes.</p>
<p>O objetivo aqui é mapeamento, não tutorial. Por onde cada família anda na operação, e como elas se conectam pra entregar coisas concretas. Vou seguir a ordem do cheatsheet só nas primeiras seções, depois agrupo por aplicação porque na vida real as fórmulas raramente aparecem sozinhas. Pra não perder o fio, o mapa das oito caixinhas contra o que aparece nas seções abaixo:</p>
<table>
  <thead>
      <tr>
          <th>Família do cheatsheet</th>
          <th>Onde ela aparece neste post</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Estatística descritiva</td>
          <td>Correlação de Ouro, média e desvio padrão de nota dentro da rede, variância, covariância</td>
      </tr>
      <tr>
          <td>Probabilidade</td>
          <td>Bayes como forma de raciocinar sobre churn e avaliação falsa, valor esperado em decisão de investimento</td>
      </tr>
      <tr>
          <td>Álgebra linear</td>
          <td>Embedding de avaliação, multiplicação de matrizes no motor de NLP, redução de dimensão</td>
      </tr>
      <tr>
          <td>Cálculo</td>
          <td>Gradiente descendente e regra da cadeia no treino do classificador de sentimento</td>
      </tr>
      <tr>
          <td>Machine learning</td>
          <td>O próprio classificador de sentimento do motor de NLP, do treino à inferência</td>
      </tr>
      <tr>
          <td>Information theory</td>
          <td>Entropia, cross-entropy no treino, comparação de distribuição, seleção de feature</td>
      </tr>
      <tr>
          <td>Data science essentials</td>
          <td>Similaridade de cosseno entre embeddings, z-score, leitura de outlier</td>
      </tr>
      <tr>
          <td>Séries temporais</td>
          <td>Moving average nos alarmes de custo e de concorrência, suavização e previsão</td>
      </tr>
  </tbody>
</table>
<h2 id="a-correlação-de-ouro-estatística-descritiva-como-bússola-comercial">A correlação de ouro: estatística descritiva como bússola comercial</h2>
<p>Começo pelo caso mais simples e mais importante. Nas análises internas que deram origem ao que a gente chama de Correlação de Ouro, loja com nota média mais alta no Google Business Profile também aparece com mais pedidos de rota gerados (os cliques de &ldquo;como chegar&rdquo;). E o efeito observado é grande o bastante pra ninguém ignorar: a cada 0,1 estrela adicional, aparece em média uma diferença de 8,8% nos pedidos de rota.</p>
<p>Vale separar as duas medidas, porque elas são confundidas o tempo todo, inclusive por mim em conversa apressada. Pearson mede a força e a direção da associação linear, num coeficiente que vive entre −1 e 1. Ele não cospe percentual por 0,1 estrela. O número de 8,8% vem da análise do tamanho do efeito, não do coeficiente. São leituras complementares que respondem perguntas diferentes: uma diz o quanto as duas variáveis andam juntas, a outra diz o quanto isso pesa na prática. Juntas, elas mostram que reputação não é apenas imagem: ela anda junto com indicador concreto de performance.</p>
<p>A fórmula em si é trivial. Correlação de Pearson é a covariância entre as duas variáveis normalizada pelo produto dos desvios padrão de cada uma, e está na primeira página de qualquer livro de estatística:</p>
<div class="formula">
$$ r_{xy} = \frac{\operatorname{cov}(X,Y)}{\sigma_X\,\sigma_Y} = \frac{\sum_{i=1}^{n}(x_i-\bar{x})(y_i-\bar{y})}{\sqrt{\sum_{i=1}^{n}(x_i-\bar{x})^2}\;\sqrt{\sum_{i=1}^{n}(y_i-\bar{y})^2}} $$
</div>
<p class="formula-nota">Aqui \(x\) é a nota média da loja e \(y\) o volume de pedidos de rota. O numerador mede se as duas variáveis se afastam da própria média na mesma direção; o denominador tira a escala, e é por isso que o resultado sempre cai entre −1 e 1.</p>
<p>Mas a aplicação é tudo menos trivial. Descobrir esse ponto, validar que ele se mantém em diferentes segmentos de varejo, sustentar ele dentro de reunião com CFO de cliente, e usar ele como bússola pra decisão de investimento em programa de reputação. Aí mora o valor real da fórmula.</p>
<p>Em paralelo a Pearson, a operação usa o resto da família descritiva o tempo inteiro. Média e desvio padrão pra entender distribuição de notas dentro de uma rede de cliente, identificando lojas que destoam pra cima ou pra baixo. Variância pra dimensionar o quanto os dados se espalham em volta da média, que é o insumo que depois alimenta erro padrão, intervalo de confiança e teste.</p>
<div class="formula">
$$ \sigma^2 = \frac{1}{n}\sum_{i=1}^{n}(x_i-\bar{x})^2 \qquad \operatorname{cov}(X,Y) = \frac{1}{n}\sum_{i=1}^{n}(x_i-\bar{x})(y_i-\bar{y}) $$
</div>
<p class="formula-nota">Variância e covariância lado a lado, na forma populacional. A segunda é a primeira com duas variáveis em vez de uma: troque o quadrado pelo produto dos dois desvios e a mesma conta passa a medir movimento conjunto. Em amostra, o divisor vira \(n-1\).</p>
<p>Ela sozinha não responde se a diferença entre dois grupos é sinal ou ruído, mas sem ela não dá nem pra fazer a pergunta direito. Covariância pra rastrear como pares de variáveis se movem juntos. Estatística descritiva é o que separa &ldquo;achismo de loja&rdquo; de &ldquo;leitura de loja&rdquo;, e essa diferença sustenta praticamente toda conversa estratégica entre Harmo e cliente.</p>
<h2 id="álgebra-linear-e-cálculo-o-motor-de-nlp-por-dentro">Álgebra linear e cálculo: o motor de NLP por dentro</h2>
<p>O motor de NLP da Harmo classifica sentimento de avaliação, extrai aspectos mencionados (atendimento, produto, preço, ambiente) e dá suporte a respostas em escala. Tudo isso é álgebra linear e cálculo combinados, em arquiteturas que aprenderam a fazer essas operações em paralelo na GPU.</p>
<p>Cada avaliação que chega vira um vetor numérico de centenas de dimensões através de um embedding. Comparar duas avaliações pra ver se são parecidas pode ser feito calculando a similaridade de cosseno entre os vetores delas:</p>
<div class="formula">
$$ \operatorname{sim}(\mathbf{a},\mathbf{b}) = \cos\theta = \frac{\mathbf{a}\cdot\mathbf{b}}{\lVert\mathbf{a}\rVert\,\lVert\mathbf{b}\rVert} = \frac{\sum_{i=1}^{n} a_i b_i}{\sqrt{\sum_{i=1}^{n} a_i^2}\;\sqrt{\sum_{i=1}^{n} b_i^2}} $$
</div>
<p class="formula-nota">Note que é a mesma estrutura da correlação de Pearson: produto no numerador, normalização pelas magnitudes no denominador. A diferença é que Pearson centra os dados na média antes, e o cosseno não. Por isso ele mede ângulo entre vetores e ignora o tamanho deles, que é exatamente o que se quer quando uma avaliação é longa e a outra curta.</p>
<p>Agrupar avaliações por similaridade pra entender padrões emergentes cai no mesmo terreno: distância euclidiana ou cosseno aplicados em larga escala, e qual das duas usar depende de como o espaço vetorial foi construído.</p>
<div class="formula">
$$ d(\mathbf{a},\mathbf{b}) = \sqrt{\sum_{i=1}^{n}(a_i-b_i)^2} $$
</div>
<p class="formula-nota">A euclidiana mede distância absoluta entre dois pontos; o cosseno mede direção. Duas avaliações sobre o mesmo assunto, uma detalhada e outra em duas linhas, ficam longe na euclidiana e perto no cosseno.</p>
<p>Identificar avaliações que poderiam ser respondidas com um mesmo template (porque tratam do mesmo tema) é busca de vizinhança no espaço de embedding.</p>
<p>A multiplicação de matrizes é a operação de base de tudo isso. Cada camada de um modelo neural moderno tem, no núcleo, uma matriz de pesos multiplicando uma matriz de entrada. Modelos de linguagem que rodam por trás de classificadores e extratores fazem isso milhares de vezes por inferência. A fórmula é esta, e ela descreve uma das operações que dominam o custo computacional da inferência desses modelos:</p>
<div class="formula">
$$ (AB)_{ij} = \sum_{k=1}^{n} A_{ik}\,B_{kj} $$
</div>
<p class="formula-nota">Uma multiplicação de matrizes de tamanho \(n\) custa na ordem de \(n^3\) operações na forma ingênua. É essa cúbica que explica por que inferência de modelo grande é caro, e por que GPU existe.</p>
<p>E quando o problema é o oposto, reduzir dimensão em vez de multiplicá-la, PCA e eigenvalues são o caminho clássico da família: comprimir dezenas ou centenas de métricas de loja em componentes que preservam a parcela de variância definida como suficiente pra aquela análise. Quanto de variância sobra não é dado da natureza, depende de quantos componentes você mantém e do threshold que decidiu adotar.</p>
<p>O cálculo entra no momento de treinar. Treinar um classificador de sentimento é minimizar uma função de erro, e minimizar uma função de erro é exatamente o gradiente descendente que descrevi no post anterior, repetido numa escala que seria impossível acompanhar manualmente. Derivada parcial em cada peso da rede, retropropagada pela regra da cadeia, parâmetro por parâmetro, batch por batch.</p>
<div class="formula">
$$ \theta_{t+1} = \theta_t - \eta\,\nabla_\theta L(\theta_t) \qquad \frac{\partial L}{\partial w} = \frac{\partial L}{\partial a}\cdot\frac{\partial a}{\partial z}\cdot\frac{\partial z}{\partial w} $$
</div>
<p class="formula-nota">À esquerda, o passo do gradiente descendente: ande na direção contrária à do gradiente, num tamanho de passo \(\eta\). À direita, a regra da cadeia, que é como o erro medido na saída chega até um peso lá no meio da rede. A retropropagação é essa multiplicação aplicada camada a camada, de trás pra frente.</p>
<p>O Cálculo II da prova final virou infraestrutura silenciosa do classificador. A regra da cadeia que parecia exercício de aula virou o algoritmo de retropropagação que treina as redes neurais modernas.</p>
<h2 id="séries-temporais-e-detecção-de-anomalia-matemática-que-dispara-alarme">Séries temporais e detecção de anomalia: matemática que dispara alarme</h2>
<p>A operação gera séries temporais o tempo inteiro. Volume de avaliações por hora, latência de microserviço, custo AWS por serviço por dia, NPS rolando ao longo da semana, taxa de resposta a avaliações por loja. Boa parte das anomalias que disparam alarme operacional aqui dentro sai de alguma combinação de três fórmulas dessa família, quase sempre com uma comparação contra baseline em cima.</p>
<p>Moving average é o ponto de partida. Você compara o valor de hoje com a média rolling de 7 ou 14 dias. Se o valor escapa de uma faixa razoável, é sinal.</p>
<div class="formula">
$$ \mathrm{MA}_t = \frac{1}{k}\sum_{i=1}^{k} x_{t-i} \qquad \Delta_t = \frac{x_t - \mathrm{MA}_t}{\mathrm{MA}_t} $$
</div>
<p class="formula-nota">À esquerda a média dos \(k\) períodos anteriores; à direita o desvio percentual do valor de hoje contra ela. É literalmente o que os nossos alarmes calculam, com \(k = 7\) e limite em \(\Delta_t > 0{,}5\).</p>
<p>Aplicado: o <a href="/blog/2026-06-03-relatorios-custo-aws-cronjob-eks/">robô diário de cost tracking</a> compara o custo do dia com a média dos 7 dias anteriores e abre task automaticamente quando um serviço estoura 50% acima dela, com piso absoluto em dólar pra não alarmar sobre centavos. O alarme sobre concorrência de lambda contra baseline rolling de 7 dias, que ficou de pé depois do <a href="/blog/2026-06-05-loop-improdutivo-step-functions-6x-aws/">postmortem do loop em Step Functions</a>, é moving average aplicado em métrica de invocação. Nos dois casos o mecanismo é desvio percentual contra a média móvel, não coisa mais sofisticada que isso.</p>
<p>Z-score é o irmão mais formal dessa abordagem. Normaliza o valor pelo desvio padrão da série e pergunta &ldquo;quantos desvios padrão isso está fora da média?&rdquo;.</p>
<div class="formula">
$$ z_t = \frac{x_t - \mu}{\sigma} $$
</div>
<p class="formula-nota">A diferença entre esta fórmula e a de cima é o denominador, e é ela que separa as duas coisas: dividir pela média dá desvio percentual, dividir pelo desvio padrão dá z-score. Só a segunda é comparável entre séries de escalas diferentes.</p>
<p>A distinção importa na hora de nomear o que você tem: comparar valor contra baseline só vira z-score quando você divide pelo desvio padrão, e a maior parte dos alarmes que citei acima para no desvio percentual mesmo. Em série suficientemente estável, a gente usa valor acima de 3 como sinal forte e acima de 4 como caso extremo. É heurística operacional, não regra universal, e em série com cauda pesada ou sazonalidade forte esses cortes enganam. Onde isso encaixa no nosso mundo: detectar avaliação suspeita (loja recebendo 50 avaliações em uma hora quando o normal seriam 5), flagrar comportamento de pesquisa anômalo, investigar caso extremo antes de calcular correlação, separando erro de coleta de variação legítima pra não distorcer a leitura. Esse último ponto merece cuidado: loja que explodiu em avaliação porque tomou fraude é uma coisa, loja que explodiu porque abriu num shopping novo é outra, e jogar as duas no mesmo filtro automático é jogar informação fora.</p>
<p>Exponential smoothing e autocorrelação entram na previsão. Estimar quantos pedidos de rota uma loja deve gerar nas próximas duas semanas, dado o histórico dela e a sazonalidade conhecida.</p>
<div class="formula">
$$ s_t = \alpha\,x_t + (1-\alpha)\,s_{t-1} \qquad \rho_k = \frac{\sum_{t=k+1}^{n}(x_t-\bar{x})(x_{t-k}-\bar{x})}{\sum_{t=1}^{n}(x_t-\bar{x})^2} $$
</div>
<p class="formula-nota">A suavização exponencial dá peso decrescente ao passado, e \(\alpha\) decide o quanto o valor de hoje manda na estimativa. A autocorrelação é a correlação da série com ela mesma deslocada em \(k\) períodos: é assim que sazonalidade semanal aparece como pico em \(k = 7\).</p>
<p>Capacity planning de processamento em janelas de pico, baseado em volume previsto de avaliações. Quando o time fala em &ldquo;previsão suavizada&rdquo;, está aplicando exponential smoothing, mesmo quando ninguém chama por esse nome.</p>
<h2 id="probabilidade-e-information-theory-decisão-sob-incerteza">Probabilidade e information theory: decisão sob incerteza</h2>
<p>A última camada que vale destacar mistura duas famílias: probabilidade e information theory, as ferramentas que sustentam decisão quando não há certeza completa, o que é praticamente sempre.</p>
<p>Bayes formaliza uma ideia que aparece o tempo inteiro em decisão sob incerteza: você parte de uma estimativa anterior e atualiza ela quando chega evidência nova. Estimar a probabilidade de um cliente fazer churn dado o histórico de NPS dele é um problema desse formato. Atualizar a probabilidade de uma avaliação ser falsa dado um padrão suspeito de IP, conta e timestamp também. Uma ressalva que eu mesmo já atropelei: o modelo cuspir uma probabilidade condicional no final não significa que ele seja bayesiano por dentro. Bayes aqui é o modo de raciocinar, não a descrição da implementação. A fórmula é compacta, três símbolos arrumados em uma razão, e esse modo de raciocinar cobre boa parte do nosso trabalho de classificação probabilística.</p>
<div class="formula">
$$ P(A \mid B) = \frac{P(B \mid A)\,P(A)}{P(B)} $$
</div>
<p class="formula-nota">Lendo da direita pra esquerda: você tinha uma crença inicial \(P(A)\), chegou a evidência \(B\), e a razão ajusta essa crença conforme a evidência é mais ou menos esperada. Aplicado a avaliação falsa: \(A\) é "a avaliação é fraudulenta" e \(B\) é o padrão suspeito de IP, conta e horário.</p>
<p>Valor esperado é mais simples e igualmente potente. Se uma intervenção em loja tem 30% de chance de gerar 10 pedidos de rota adicionais e 70% de chance de gerar 2, o valor esperado é 4,4 pedidos por intervenção.</p>
<div class="formula">
$$ E[X] = \sum_{i} p_i\,x_i = 0{,}3 \times 10 + 0{,}7 \times 2 = 4{,}4 $$
</div>
<p class="formula-nota">Toda a força dessa fórmula está em obrigar você a escrever a probabilidade que já estava na sua cabeça. Depois de escrita, ela pode ser discutida, e é aí que a conversa sai do achismo.</p>
<p>Multiplica isso por escala de milhares de lojas e você tem decisão de investimento orçamentário ancorada em matemática, não em achismo. Aparece em conversa de pricing, de roadmap de feature, de priorização de cliente.</p>
<p>Entropy, cross-entropy e KL-divergence são onde a coisa fica densa, mas valem o esforço. Entropia mede o quão incerto é um sistema.</p>
<div class="formula">
$$ H(p) = -\sum_{i} p_i \log p_i \qquad H(p,q) = -\sum_{i} p_i \log q_i \qquad D_{\mathrm{KL}}(p \parallel q) = \sum_{i} p_i \log \frac{p_i}{q_i} $$
</div>
<p class="formula-nota">As três são a mesma soma com peças trocadas. A entropia usa a distribuição contra si mesma; a cross-entropy troca o segundo termo pela distribuição prevista, e é isso que a torna função de erro; a KL é a diferença entre as duas, ou seja o custo de usar \(q\) quando a verdade é \(p\). Vale reparar que ela não é simétrica: trocar \(p\) e \(q\) de lugar muda o número.</p>
<p>Cross-entropy é uma das funções de erro mais usadas no treinamento de classificador com múltiplas classes; nos classificadores neurais de sentimento, é ela que o treino minimiza, debaixo dos panos. KL-divergence não é medida simétrica de semelhança: ela mede o quanto uma distribuição diverge de uma distribuição de referência, e a ordem importa, porque KL de A pra B não dá o mesmo número que KL de B pra A. Perguntar &ldquo;o quanto a distribuição de sentimento da loja A se afasta da distribuição da rede dela?&rdquo; é o tipo de pergunta que essa família responde, com leitura comercial direta. Quando o que você quer é comparar duas lojas de igual pra igual, aí é outra métrica, e a escolha muda a interpretação do resultado. Mutual information vira ferramenta de feature selection: qual variável carrega mais informação sobre o que queremos prever, e qual é redundante?</p>
<div class="formula">
$$ I(X;Y) = \sum_{x}\sum_{y} p(x,y) \log \frac{p(x,y)}{p(x)\,p(y)} $$
</div>
<p class="formula-nota">O termo dentro do logaritmo compara a distribuição conjunta com o que ela seria se as duas variáveis fossem independentes. Quando dá zero, a variável não diz nada sobre o alvo. Note que é uma KL disfarçada, entre a conjunta real e a conjunta hipotética das independentes.</p>
<p>Information theory parece esotérica até o momento em que você precisa selecionar features pra um modelo com centenas de candidatas. Aí ela vira ferramenta especialmente útil pra separar quais variáveis carregam informação relevante sobre o que você quer prever e quais são redundância cara de carregar. Registro honesto: aqui isso é aplicação possível da família, não etapa fixa do pipeline de hoje.</p>
<h2 id="fechamento">Fechamento</h2>
<p>O cheatsheet tem oito caixinhas. Cada uma é uma família de ferramenta matemática. A leitura mais útil dele não é &ldquo;preciso decorar tudo isso pra fazer data science&rdquo;. É &ldquo;cada uma dessas famílias tem aplicação concreta em produção, e a maioria delas é exatamente o que sustenta o que você consome hoje em qualquer plataforma SaaS séria&rdquo;.</p>
<p>Na Harmo, a estatística descritiva sustenta a Correlação de Ouro que orienta a proposta de valor inteira. A álgebra linear e o cálculo sustentam o motor de NLP que processa avaliações em escala. As séries temporais sustentam os alarmes operacionais que evitam que incidente vire desastre. A probabilidade e a information theory organizam a decisão sob incerteza que define onde investir o tempo do time e o orçamento dos clientes. Cada caixinha do cheatsheet aparece em algum momento do dia operacional, mesmo quando ninguém chama as coisas por esses nomes.</p>
<p>Fechando a amarração do post do alpinista no nevoeiro: aquele aluno de Cálculo II que torcia o nariz pra derivada parcial estava aprendendo o motor de uma das oito famílias. As outras sete, ele já viu, em Cálculo I, em Estatística, em Álgebra Linear, em Probabilidade. Tudo o que parecia abstração de prova é, dez anos depois, infraestrutura silenciosa do que ele usa pra trabalhar. A diferença entre o aluno que aproveita isso na carreira e o que não aproveita é só saber onde olhar.</p>
<p>Qual dessas oito famílias você aplica em produção sem enxergar como matemática? Aparição disfarçada é o caso comum: regra de negócio que esconde uma probabilidade condicional, métrica de dashboard que esconde uma moving average, threshold de alarme que esconde uma comparação contra baseline. Rastrear essas é o exercício mais útil que o cheatsheet permite.</p>
]]></content:encoded><category>matemática</category><category>ai</category><category>machine-learning</category><category>data-science</category><category>educação</category></item><item><title>MWAA orquestrando workers em Go no EKS: a stack que sustenta nossos pipelines de dados</title><link>https://rifeli.dev/blog/2026-07-25-airflow-eks-go-orquestracao-pipelines/</link><pubDate>Sat, 25 Jul 2026 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/2026-07-25-airflow-eks-go-orquestracao-pipelines/</guid><description>A topologia que combina MWAA (Airflow gerenciado pela AWS) como scheduler em Python e EKS como runtime dos workers pesados em Go. Por que escolhemos esse split, como as DAGs disparam pods em Go via KubernetesPodOperator, e os gotchas operacionais específicos do MWAA + EKS.</description><content:encoded><![CDATA[<img id="image-custom" src="/images/posts/b8dbce8d-80f0-41dd-ad4c-fde4573aef81.png" alt="Um nó central no topo distribuindo ramificações até dezenas de contêineres enfileirados, alguns acesos em amarelo" />
<p id="image-legend">Um cérebro só decide a ordem; muitas mãos executam. O scheduler resolve o grafo de dependências e dispara os workers que fazem o trabalho pesado.</p>
<h2 id="introdução">Introdução</h2>
<p>Airflow é Python por natureza. MWAA é o Airflow gerenciado da AWS. EKS é onde nossos workloads pesados rodam, em Go (cluster Kubernetes). A combinação dos três não é a mais óbvia, mas é a que sustenta hoje a maior parte dos pipelines de dados que rodam por trás da Plataforma Harmo. Esse post conta o porquê dessa escolha e como as peças se encaixam.</p>
<p>A escala que essa stack sustenta hoje: mais de 25 DAGs ativas em produção, executando cerca de mil tasks por dia com taxa de falha de 0,35% no último mês. Dessas tasks, uma média de 500 por dia vira pod de Go no EKS, cerca de 15 mil pods em 30 dias, cuidando da coleta, do processamento e da sincronização das mais de 300 mil avaliações públicas que entram por mês na plataforma.</p>
<p>Antes de entrar na arquitetura, vale dizer o que isso substituiu. A geração anterior era cronjobs agendando Lambdas em Node.js, coladas por filas SQS, e mais tarde Step Functions coordenando os fluxos de coleta. O MWAA entrou em março de 2024, mas não houve corte: parte do legado coexistiu por mais dois anos.</p>
<h2 id="por-que-airflow">Por que Airflow</h2>
<p>DAG como código em Python virou o ganho mais imediato. Cadeias de dependência entre tasks que viravam código procedural feio em cron + script bash ganharam estrutura declarativa: quem depende de quem, qual o critério de sucesso, qual a política de retry, qual a janela de execução. UI nativa pra backfill, retry manual, visualização de dependências, gestão de SLA. Ecossistema maduro de operators que cobre a maioria dos casos de integração comum.</p>
<p>A maturidade do Airflow trouxe um custo conhecido: ele assume Python no plano da orquestração. E aí entra a decisão de manter Python apenas no scheduler, deixando o trabalho pesado fora dele.</p>
<h2 id="por-que-mwaa-pro-scheduler-e-eks-pro-runtime">Por que MWAA pro scheduler e EKS pro runtime</h2>
<p>A primeira pergunta foi onde hospedar o Airflow em si. Self-hosted via Helm chart oficial no EKS era opção possível, mas trazia operação inteira do Airflow pra dentro do time: upgrade de versão, gerenciamento de scheduler/web server/workers, backup de metadata DB, segurança. MWAA absorve boa parte dessa operação como serviço gerenciado, em troca de menos flexibilidade fina. Pra um time que quer Airflow como ferramenta e não como produto operado, vale a troca.</p>
<p>Nosso ambiente é um mw1.medium rodando Airflow 2.10.1, com dois schedulers e workers Celery escalando de um a cinco. Classe modesta de propósito: o MWAA só coordena. Se o ambiente precisasse ser grande, seria sinal de que trabalho pesado está vazando pra dentro do scheduler.</p>
<p>A segunda pergunta foi onde rodar o trabalho pesado. A Harmo já roda mais de 50 microservices em Go no EKS, com observabilidade, autoscaling, secrets management e quotas de recurso compartilhados. Subir compute pro trabalho pesado em outro lugar (Lambda, ECS, Batch) seria duplicação de operação sem ganho. Então a decisão foi natural: scheduler no MWAA, workload no EKS.</p>
<p>O ponto que destrava a topologia é a integração entre os dois. MWAA tem permissão de chamar a API do EKS via execution role configurada com IAM. Isso permite que o <strong>KubernetesPodOperator</strong> dentro de uma DAG aponte pro nosso cluster EKS e suba pods lá, mesmo o Airflow não morando dentro do cluster. Cada task pesada vira um pod Go separado no EKS, disparado pelo MWAA via API call de pod create. O scheduler nunca hospeda o processo pesado: ele acompanha um pod e um estado.</p>
<h2 id="por-que-go-pros-workers">Por que Go pros workers</h2>
<p>Workers do Airflow padrão em Python sustentam a coordenação, não o trabalho pesado. Pro perfil dos nossos workers, que é muito I/O concorrente, paginação e escrita em massa em banco, Go tem entregado throughput maior, consumo previsível e uma operação que o time já domina. Footprint de container fica pequeno, startup time fica curto, e o padrão de worker pool com errgroup, context e cancelamento limpo <a href="/blog/2026-05-27-concorrencia-worker-pools-go/">já virou rotina por aqui</a>.</p>
<p>Quatro categorias cobrem quase tudo que roda em Go por aqui. Coleta de avaliações em fontes externas (Google, iFood, TripAdvisor, etc), em lotes que chegam via arquivo no S3. Processamento de texto e IA sobre o que foi coletado, extraindo sentimento, termos e categorias, que é onde está o maior volume de pods do dia. Sincronização de feeds com a ponta do Google: catálogo, cardápio, ofertas. E disparo de notificações e consolidação de relatórios. Todos os workers seguem o mesmo formato: binário único compilado pra arm64, imagem mínima, <code>cmds=[&quot;./main&quot;]</code> como entrypoint.</p>
<p>Em paralelo, mantemos workers em Python pra coisas em que Python ganha de Go por bibliotecas (manipulação de DataFrame com pandas, ML clássico com scikit-learn, integrações com bibliotecas científicas). A escolha de linguagem por task é feita conscientemente, não por inércia.</p>
<h2 id="a-topologia">A topologia</h2>
<p>O fluxo típico de uma task pesada é o seguinte. DAG em Python define o <strong>KubernetesPodOperator</strong> com a imagem do worker em Go, parâmetros de entrada via variáveis de ambiente ou arquivo de config, e política de retry/timeout. Airflow dispara o pod no cluster, o pod roda o binário Go que faz o trabalho, escreve resultado em destino persistente (S3, Postgres, Kafka, dependendo da task) e termina. O operator acompanha o estado do pod até o fim e traduz esse término em sucesso ou falha da task, e é isso que decide se o fluxo segue pra próxima ou entra em retry.</p>
<p>Uma DAG de coleta real, enxuta e anonimizada, fica assim:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#ff79c6">import</span> datetime
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">from</span> airflow <span style="color:#ff79c6">import</span> DAG
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">from</span> airflow.providers.cncf.kubernetes.operators.pod <span style="color:#ff79c6">import</span> KubernetesPodOperator
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">from</span> airflow.utils.task_group <span style="color:#ff79c6">import</span> TaskGroup
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">from</span> kubernetes.client <span style="color:#ff79c6">import</span> models <span style="color:#ff79c6">as</span> k8s
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>dag <span style="color:#ff79c6">=</span> DAG(
</span></span><span style="display:flex;"><span>    dag_id<span style="color:#ff79c6">=</span><span style="color:#f1fa8c">&#34;coleta_avaliacoes&#34;</span>,
</span></span><span style="display:flex;"><span>    start_date<span style="color:#ff79c6">=</span>datetime<span style="color:#ff79c6">.</span>datetime(<span style="color:#bd93f9">2026</span>, <span style="color:#bd93f9">5</span>, <span style="color:#bd93f9">5</span>),
</span></span><span style="display:flex;"><span>    schedule<span style="color:#ff79c6">=</span><span style="color:#f1fa8c">&#34;0 4,17 * * *&#34;</span>,
</span></span><span style="display:flex;"><span>    catchup<span style="color:#ff79c6">=</span><span style="color:#ff79c6">False</span>,
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">def</span> <span style="color:#50fa7b">cria_pod</span>(lote_id, arquivo, versao):
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">return</span> KubernetesPodOperator(
</span></span><span style="display:flex;"><span>        dag<span style="color:#ff79c6">=</span>dag,
</span></span><span style="display:flex;"><span>        task_id<span style="color:#ff79c6">=</span><span style="color:#f1fa8c">f</span><span style="color:#f1fa8c">&#34;coleta_avaliacoes_</span><span style="color:#f1fa8c">{</span>lote_id<span style="color:#f1fa8c">}</span><span style="color:#f1fa8c">&#34;</span>,
</span></span><span style="display:flex;"><span>        namespace<span style="color:#ff79c6">=</span><span style="color:#f1fa8c">&#34;&lt;NAMESPACE&gt;&#34;</span>,
</span></span><span style="display:flex;"><span>        image<span style="color:#ff79c6">=</span><span style="color:#f1fa8c">f</span><span style="color:#f1fa8c">&#34;&lt;REGISTRY&gt;/coletor-avaliacoes:</span><span style="color:#f1fa8c">{</span>versao<span style="color:#f1fa8c">}</span><span style="color:#f1fa8c">&#34;</span>,
</span></span><span style="display:flex;"><span>        cmds<span style="color:#ff79c6">=</span>[<span style="color:#f1fa8c">&#34;./main&#34;</span>],
</span></span><span style="display:flex;"><span>        arguments<span style="color:#ff79c6">=</span>[<span style="color:#f1fa8c">&#34;-data&#34;</span>, arquivo],
</span></span><span style="display:flex;"><span>        annotations<span style="color:#ff79c6">=</span>{
</span></span><span style="display:flex;"><span>            <span style="color:#f1fa8c">&#34;karpenter.sh/do-not-disrupt&#34;</span>: <span style="color:#f1fa8c">&#34;true&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#f1fa8c">&#34;cluster-autoscaler.kubernetes.io/safe-to-evict&#34;</span>: <span style="color:#f1fa8c">&#34;false&#34;</span>,
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>        container_resources<span style="color:#ff79c6">=</span>k8s<span style="color:#ff79c6">.</span>V1ResourceRequirements(
</span></span><span style="display:flex;"><span>            requests<span style="color:#ff79c6">=</span>{<span style="color:#f1fa8c">&#34;cpu&#34;</span>: <span style="color:#f1fa8c">&#34;500m&#34;</span>, <span style="color:#f1fa8c">&#34;memory&#34;</span>: <span style="color:#f1fa8c">&#34;1024Mi&#34;</span>},
</span></span><span style="display:flex;"><span>            limits<span style="color:#ff79c6">=</span>{<span style="color:#f1fa8c">&#34;cpu&#34;</span>: <span style="color:#f1fa8c">&#34;500m&#34;</span>, <span style="color:#f1fa8c">&#34;memory&#34;</span>: <span style="color:#f1fa8c">&#34;1024Mi&#34;</span>},
</span></span><span style="display:flex;"><span>        ),
</span></span><span style="display:flex;"><span>        get_logs<span style="color:#ff79c6">=</span><span style="color:#ff79c6">True</span>,
</span></span><span style="display:flex;"><span>        is_delete_operator_pod<span style="color:#ff79c6">=</span><span style="color:#ff79c6">True</span>,
</span></span><span style="display:flex;"><span>        retries<span style="color:#ff79c6">=</span><span style="color:#bd93f9">3</span>,
</span></span><span style="display:flex;"><span>        startup_timeout_seconds<span style="color:#ff79c6">=</span><span style="color:#bd93f9">350</span>,
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">with</span> TaskGroup(<span style="color:#f1fa8c">&#34;coleta&#34;</span>, dag<span style="color:#ff79c6">=</span>dag) <span style="color:#ff79c6">as</span> grupo:
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">for</span> i, arquivo <span style="color:#ff79c6">in</span> <span style="color:#8be9fd;font-style:italic">enumerate</span>(lista_arquivos_do_lote(), start<span style="color:#ff79c6">=</span><span style="color:#bd93f9">1</span>):
</span></span><span style="display:flex;"><span>        cria_pod(i, arquivo, versao_imagem())
</span></span></code></pre></div><p>As annotations de anti-disrupção e o requests igual ao limits não são decoração, e cada um resolve um problema diferente: cada uma dessas linhas é cicatriz de incidente, e conto a história na seção das cicatrizes. Boa parte das DAGs nem instancia o <strong>KubernetesPodOperator</strong> direto: usa um operator interno que o estende com os defaults (namespace, recursos, proteção contra disrupção), pra que DAG nova não reinvente configuração.</p>
<p>Pra dados pequenos entre tasks (IDs, timestamps, métricas de resumo), usamos XCom. Pra dados grandes (datasets, payloads completos), o intermediário é storage externo: o pod escreve em S3, a próxima task lê o path do XCom e busca o conteúdo de lá. A regra por aqui é XCom carrega referência, não payload. Isso é decisão da nossa arquitetura, não limitação universal do Airflow: o XCom aguenta mais do que a gente deixa passar por ele, mas dado grande no metadata DB é acoplamento que a gente não quer.</p>
<h2 id="as-cicatrizes-da-arquitetura">As cicatrizes da arquitetura</h2>
<p>Cinco aprendizados que custaram tempo.</p>
<p><strong>Eviction no meio da coleta.</strong> Uma das nossas coletoras leva cerca de meia hora pra varrer mais de 2 mil estabelecimentos. Em maio, execuções começaram a morrer no meio do caminho: numa delas, 1.189 de 2.037 estabelecimentos se perderam quando o pod foi despejado por <code>EvictionByEvictionAPI</code>. A consolidação do Karpenter tinha decidido que aquele nó podia ser drenado, e os pods rodavam em QoS Burstable, sem nenhuma proteção declarada. A correção saiu em duas ondas no mesmo dia, e ela junta dois mecanismos que a gente aprendeu a não confundir.</p>
<p>A annotation <code>karpenter.sh/do-not-disrupt</code> fala com o Karpenter: pede pra ele não escolher aquele nó pra consolidação ou drain voluntário. Requests iguais a limits fazem outra coisa: promovem o pod a QoS Guaranteed, o que reduz o risco de eviction quando o nó entra em pressão de recurso. Nenhuma das duas cobre o buraco da outra. QoS Guaranteed sozinho não impede o Karpenter de consolidar ou drenar o nó embaixo do pod, e <code>do-not-disrupt</code> não protege contra toda disrupção involuntária ou forçada: se o nó morre, se alguém drena na força, o pod cai igual. A <code>cluster-autoscaler.kubernetes.io/safe-to-evict: &quot;false&quot;</code> que aparece no exemplo entrou na mesma onda e continua lá, mas ela fala com o Cluster Autoscaler do Kubernetes, não com o Karpenter, então não é ela que segura a consolidação por aqui.</p>
<p>Na segunda onda, espelhamos as duas proteções nas duas coletoras mais longas do parque, com execuções de duas a três horas, que eram alvos ainda maiores. Pod de task longa sem proteção explícita é aposta contra o autoscaler, e o autoscaler ganha.</p>
<p><strong>O retry do Airflow reinicia do zero.</strong> Todos os nossos pods rodam com <code>retries=3</code>, e o incidente de eviction confirmou na prática: task despejada aos 90% refaz 100%. Isso transforma idempotência em pré-requisito de design, não em refinamento. Cada worker recebe um lote fechado via arquivo no S3 e precisa poder reprocessar o mesmo lote sem duplicar no destino. Não temos registro de duplicação em produção, e quero que continue assim, porque isso é propriedade de desenho, não de sorte.</p>
<p><strong>O CLI do MWAA não conhece as suas DAGs.</strong> Rodar <code>dags list</code> pelo endpoint de CLI do MWAA retornava 3 das nossas DAGs, com um <code>ModuleNotFoundError: No module named 'airflow.providers.cncf'</code> pras outras. O container que atende o CLI não tem o provider do Kubernetes instalado, então falha ao parsear qualquer arquivo que use <strong>KubernetesPodOperator</strong>, enquanto o scheduler parseia e executa tudo normalmente. <code>dags details</code>, que consulta o banco de metadados em vez de parsear arquivo, funciona pra todas. Custa alguns minutos de pânico até perceber que é artefato do CLI, não DAG quebrada.</p>
<p><strong>Logs em dois lugares por construção.</strong> Com <code>get_logs=True</code>, o Airflow puxa o stdout do pod e reemite no log da própria task, que vai pro CloudWatch no log group de tasks do MWAA. Se o worker também envia log direto pro CloudWatch, a mesma linha passa a existir em dois grupos, com dois caminhos de busca diferentes. Vale decidir cedo qual é a fonte da verdade na hora do incidente, antes que a resposta seja descoberta às 3 da manhã.</p>
<p><strong>A DAG que consulta a AWS no parse.</strong> Nas DAGs de coleta, a lista de pods é montada no nível do módulo: a cada ciclo do DAG processor, o arquivo consulta o ECR pra resolver a versão da imagem e lê o lote no S3 pra decidir a topologia. Funciona, mas acopla o parse das DAGs à disponibilidade de duas APIs da AWS e paga essas chamadas continuamente, não só na execução. Se o ECR soluçar, o scheduler enxerga DAG quebrada. É dívida assumida, e registrar ela num post público é um jeito de não fingir que não existe.</p>
<h2 id="lições-aprendidas">Lições aprendidas</h2>
<ul>
<li>
<p>Worker padronizado corta custo de manutenção. Cada worker é um binário Go compilado pra arm64 numa imagem mínima, com <code>./main</code> de entrypoint. Qualquer coisa além disso na imagem é sintoma de worker mal desenhado.</p>
</li>
<li>
<p>XCom carrega referência, não payload. Lote entra por arquivo no S3 e o XCom leva só metadado: path, contagem, versão de imagem. É decisão nossa, não limite do Airflow, e precisa ser explícita no time, não convenção implícita, senão a DAG cresce torta.</p>
</li>
<li>
<p>Idempotência vale mais que retry esperto. O retry do Airflow reinicia a task do zero, então o worker que não sabe reprocessar o próprio lote sem duplicar é uma duplicação agendada.</p>
</li>
<li>
<p>Task longa exige proteção explícita, e são duas proteções distintas. Requests iguais a limits promovem o pod a QoS Guaranteed e reduzem eviction por pressão de recurso; <code>karpenter.sh/do-not-disrupt</code> é o que pede ao Karpenter pra não consolidar o nó embaixo do pod. Burstable e sem annotation numa task de 3 horas é aposta contra o autoscaler.</p>
</li>
<li>
<p>Métrica retida não é alarme. O MWAA publica tudo em CloudWatch, mas não cria alarme nenhum por padrão. Alarme de orquestração é trabalho seu, e é o tipo de coisa que se descobre tarde.</p>
</li>
<li>
<p>Pod não é pra task trivial. Aqui, aproximadamente metade das execuções vira pod; o resto roda em PythonOperator e afins dentro do próprio worker do MWAA. Subir pod, puxar imagem e inicializar runtime pra 200ms de trabalho transforma uma task rápida numa task de 30 segundos.</p>
</li>
</ul>
<h2 id="conclusão">Conclusão</h2>
<p>A maior parte das empresas trata Airflow e Python como um pacote indivisível: orquestra em Python, executa em Python, escala em Python. Pra quem já opera workload pesado em Kubernetes, o desenho mais limpo é outro. O scheduler resolve o grafo, dispara o pod e cobra o resultado, e nesse papel restrito o Airflow é excelente. O trabalho pesado fica onde a operação já sabe rodar, observar e escalar: no cluster, em Go.</p>
<p>Não escolhemos uma stack exótica. Escolhemos limitar a responsabilidade de cada peça.</p>
<p>O scheduler coordena. O cluster executa. O worker processa.</p>
<p>E cada um faz só o trabalho que sabe fazer melhor.</p>
]]></content:encoded><category>mwaa</category><category>airflow</category><category>eks</category><category>golang</category><category>kubernetes</category><category>data-engineering</category></item><item><title>Observabilidade e alertas no EKS: fechando o loop</title><link>https://rifeli.dev/blog/2026-06-22-observabilidade-alertas-eks/</link><pubDate>Mon, 22 Jun 2026 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/2026-06-22-observabilidade-alertas-eks/</guid><description>Métricas com Prometheus, logs centralizados, alertas de custo e performance, a camada que transforma um cluster cloud-native em operação real, e como evitar os alertas que só fazem você desligar o celular.</description><content:encoded><![CDATA[<img id="image-custom" src="/images/posts/a67480be-998f-4f5d-b948-24fe0b5c531b.png" alt="Pessoa de costas diante de um painel de monitores vermelhos cheios de alertas, com um único gráfico azul legível no centro" />
<p id="image-legend">Duzentos alertas piscando, e o que importa é aquele único sinal que você consegue enxergar no meio do ruído.</p>
<h2 id="introdução">Introdução</h2>
<p>Essa é a última peça de uma série que caminhou por crise de performance no banco, batch inserts, IRSA e Pod Identity, relatórios de custo e concorrência em Go. Cada um desses posts resolveu um problema específico. Observabilidade é o que liga eles, o que transforma &ldquo;consegui consertar&rdquo; em &ldquo;vou ver chegar da próxima vez&rdquo;.</p>
<p>Vou falar do que realmente usamos na Harmo pra operar o cluster EKS: stack de métricas, estratégia de logs, e principalmente a filosofia de alerta, que é onde a maioria das operações se perde, seja por alertar demais, seja por alertar de menos.</p>
<h2 id="métricas-prometheus--grafana-é-o-default-por-um-motivo">Métricas: Prometheus + Grafana é o default por um motivo</h2>
<p>O stack Prometheus + Grafana é o default no Kubernetes por motivos práticos:</p>
<ul>
<li><strong>Integração nativa via ServiceMonitor.</strong> Qualquer serviço que exponha <code>/metrics</code> entra no scrape automaticamente.</li>
<li><strong>PromQL é a lingua franca.</strong> Aprende uma vez, usa em todo lugar.</li>
<li><strong>Alertmanager é separado de Grafana.</strong> Alerting não depende do dashboard estar respondendo.</li>
<li><strong>Custo controlado.</strong> Você paga storage e compute dos nós que já tem. Sem per-metric pricing.</li>
</ul>
<p>Na prática, instala via Helm chart <code>kube-prometheus-stack</code> (antigo Prometheus Operator), ajusta retention conforme disco disponível, e está 80% do caminho.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#ff79c6">prometheus</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">prometheusSpec</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">retention</span>: 30d
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">retentionSize</span>: <span style="color:#f1fa8c">&#34;80GB&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">storageSpec</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">volumeClaimTemplate</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">spec</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">storageClassName</span>: gp3
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">resources</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">requests</span>:
</span></span><span style="display:flex;"><span>              <span style="color:#ff79c6">storage</span>: 100Gi
</span></span></code></pre></div><p>30 dias de retention é suficiente pra investigar qualquer incidente recente. Pra análise de tendência mais longa, exporta pra S3 via Thanos ou Mimir, mas só se você tem caso real de uso. Não instala por instalar.</p>
<h2 id="logs-centralize-mas-não-tudo">Logs: centralize, mas não tudo</h2>
<p>Logs no Kubernetes têm três caminhos comuns:</p>
<table>
  <thead>
      <tr>
          <th>Solução</th>
          <th>Quando vale</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>CloudWatch Logs</td>
          <td>Stack já AWS-nativo, times pequenos, tolera latência e custo por volume</td>
      </tr>
      <tr>
          <td>Loki</td>
          <td>Stack já Grafana, times com disciplina de labels, volume alto</td>
      </tr>
      <tr>
          <td>Elasticsearch/OpenSearch</td>
          <td>Precisa full-text search agressivo, tem orçamento</td>
      </tr>
  </tbody>
</table>
<p>Usamos Loki. Motivo: integra direto com Grafana (queria dashboards correlacionando log e métrica no mesmo painel), tem custo previsível, e força disciplina de labels. Indexar só labels e ler texto bruto vira hábito que paga.</p>
<p>O erro clássico: centralizar todo log de toda aplicação de todo nível. Você paga storage por ruído. Política que adotamos:</p>
<ul>
<li><strong>Aplicação em produção</strong>: <code>INFO</code> e acima.</li>
<li><strong>Nó e infra</strong>: logs de sistema, kubelet, containerd.</li>
<li><strong>Audit log do Kubernetes</strong>: sim, sempre.</li>
<li><strong>Debug logs</strong>: só quando debug. Liga temporariamente via config, desliga depois.</li>
</ul>
<h2 id="a-estratégia-de-alerta">A estratégia de alerta</h2>
<p>Aqui mora o que mais diferencia operação boa de operação ruim. A armadilha é simples: você começa com poucos alertas, eles avisam bem, você vai ganhando confiança e adicionando mais. Em 6 meses você tem 200 alertas, metade é falso positivo, você começa a ignorar. E no dia que alerta verdadeiro chega, ele some no meio do ruído.</p>
<p>Regra que seguimos: <strong>alerta só existe se tem ação humana associada e precisa acontecer agora</strong>. Se a ação pode esperar o horário comercial, não é alerta, é uma task no board. Se não tem ação (&ldquo;a CPU subiu&rdquo;), não é alerta, é métrica no dashboard.</p>
<p>Três camadas que valem alertar:</p>
<p><strong>1. Alertas de disponibilidade (página alguém)</strong></p>
<ul>
<li>API principal retornando 5xx em taxa acima de baseline</li>
<li>Fila de processamento com idade do item mais velho acima de threshold</li>
<li>Banco indisponível ou latência de commit acima de patamar</li>
<li>Cluster sem nós prontos ou com pressure crônico</li>
</ul>
<p>Essas acordam gente de madrugada. Tem que ser tratadas como coisa séria: runbook conhecido, SLA de resposta, e postmortem depois.</p>
<p><strong>2. Alertas de anomalia (não-urgente, mas sinal)</strong></p>
<ul>
<li>Custo diário AWS acima do baseline (o CronJob do post 4 já resolve isso)</li>
<li>Taxa de erro acima de baseline mas abaixo do crítico</li>
<li>Queda súbita em throughput (queue drenando mais devagar)</li>
</ul>
<p>Esses chegam em canal de Slack, sem on-call. Se ninguém olhar em 24h, não quebra nada, mas se aparecerem três juntos, muda de gravidade.</p>
<p><strong>3. Alertas de saúde técnica (semanal)</strong></p>
<ul>
<li>Índices do banco com <code>idx_scan = 0</code> há mais de 30 dias</li>
<li>Imagens com CVE alto não atualizadas</li>
<li>Certificado vencendo em menos de 15 dias</li>
</ul>
<p>Esses viram tasks num board. Nunca alerta real, só coisa pra revisar no ritmo certo.</p>
<h2 id="exemplos-de-regra-no-prometheus">Exemplos de regra no Prometheus</h2>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#ff79c6">groups</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#ff79c6">name</span>: api-availability
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">rules</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#ff79c6">alert</span>: ApiHighErrorRate
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">expr</span>: |<span style="color:#f1fa8c">
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">          sum(rate(http_requests_total{status=~&#34;5..&#34;}[5m]))
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">          / sum(rate(http_requests_total[5m])) &gt; 0.05</span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">for</span>: 3m
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">labels</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">severity</span>: page
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">annotations</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">summary</span>: <span style="color:#f1fa8c">&#34;API com 5xx acima de 5% há 3 minutos&#34;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">runbook</span>: <span style="color:#f1fa8c">&#34;https://wiki.internal/runbook/api-5xx&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>      - <span style="color:#ff79c6">alert</span>: DbCommitLatencyHigh
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">expr</span>: pg_stat_database_commit_time_seconds &gt; 1
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">for</span>: 5m
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">labels</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">severity</span>: page
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">annotations</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">summary</span>: <span style="color:#f1fa8c">&#34;Latência de commit no Postgres &gt; 1s&#34;</span>
</span></span></code></pre></div><p>Três detalhes não-negociáveis:</p>
<ul>
<li><strong><code>for</code></strong> (duração antes de disparar): evita alarme por ruído de 30 segundos.</li>
<li><strong><code>severity</code></strong>: roteamento no Alertmanager. <code>page</code> acorda, <code>ticket</code> cria task, <code>warning</code> fica no Slack.</li>
<li><strong><code>annotations.runbook</code></strong>: link pra o que fazer. Alerta sem runbook é alerta novo, e alerta novo nunca deveria ir pra <code>page</code> na primeira vez.</li>
</ul>
<h2 id="fechando-o-loop-com-os-posts-anteriores">Fechando o loop com os posts anteriores</h2>
<p>Cada post da série deixou uma deixa pra cá:</p>
<ul>
<li><strong>Crise no Aurora</strong>: alerta em latência de commit e em <code>idx_scan = 0</code> (saúde técnica semanal) teria detectado a deterioração antes do pico.</li>
<li><strong>Batch inserts</strong>: alerta em idade do item mais velho na fila detecta regressão de throughput antes do cliente reclamar.</li>
<li><strong>IRSA / Pod Identity</strong>: alerta em <code>AccessDeniedException</code> com baseline por serviço detecta config quebrada rapidamente.</li>
<li><strong>Relatório de custo</strong>: o CronJob gera anomalia detectada, já é alerta estruturado.</li>
<li><strong>Worker pool em Go</strong>: expor métricas de goroutines ativas, queue length e erros por worker torna o pool observável.</li>
</ul>
<h2 id="lições-aprendidas">Lições aprendidas</h2>
<ul>
<li><strong>Observabilidade é investimento contínuo.</strong> Cada incidente é uma oportunidade de adicionar a métrica ou o alerta que teria avisado antes. Feito depois, ainda é feito.</li>
<li><strong>Menos alerta é mais alerta.</strong> Alerta só existe se aciona humano e tem ação. Tudo outro é dashboard ou task.</li>
<li><strong>Runbook no annotation, sempre.</strong> Alerta sem runbook é quase um alerta falso, vai chegar numa pessoa que não sabe o que fazer.</li>
<li><strong>Custo também é métrica.</strong> Tratar custo como sinal técnico, não como &ldquo;problema do financeiro&rdquo;, reduz surpresa no fim do mês.</li>
<li><strong>Loop fechado vale mais que ferramenta cara.</strong> Um stack simples e bem usado bate um stack sofisticado e abandonado.</li>
</ul>
<h2 id="fechando-a-série">Fechando a série</h2>
<p>Seis posts cobriram um corte de cloud-native operado de verdade: banco sob pressão e como reagir, consumer eficiente, permissões de pod, visibilidade de custo, concorrência na ponta, e a camada que fecha o loop: observabilidade e alerta.</p>
<p>Nada disso é novo. O valor está em colocar tudo junto e defender a decisão de não sofisticar onde não precisa. Operação boa é repetível, e repetível é o que você consegue explicar pra alguém que entrou no time ontem.</p>
<p>E no seu cluster, qual foi o último alerta que mudou como vocês operam: um que você adicionou preventivamente, ou um que só existe porque um incidente já tinha acontecido?</p>
]]></content:encoded><category>eks</category><category>observabilidade</category><category>sre</category><category>kubernetes</category><category>aws</category></item><item><title>O alpinista no nevoeiro: dez anos depois do Cálculo I, descobri que ele mora dentro de toda IA moderna</title><link>https://rifeli.dev/blog/2026-06-19-alpinista-nevoeiro-gradiente-descendente-ia/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/2026-06-19-alpinista-nevoeiro-gradiente-descendente-ia/</guid><description>Continuação espiritual do post de 2015 sobre Cálculo I. Como o gradiente descendente, ferramenta nascida das derivadas parciais do Cálculo II, virou a infraestrutura silenciosa que treina toda IA moderna. A intuição do alpinista no nevoeiro como metáfora central, fechando o arco com a minissérie recente sobre 30 dias dentro do Claude Code, slash commands e o dashboard Clawtop.</description><content:encoded><![CDATA[<img id="image-custom" src="/images/posts/b0c3e9bf-35a5-43c8-80a6-bdb15f71956b.png" alt="Silhueta de uma pessoa vendada descendo uma encosta de malha luminosa em direção a um ponto verde no fundo do vale" />
<p id="image-legend">Descer no escuro, um passo de cada vez: é assim que o modelo encontra o fundo do vale.</p>
<h2 id="introdução">Introdução</h2>
<p>Em 2015 escrevi <a href="/blog/2015-07-12-como-ir-bem-em-calculo-um/">aqui no blog sobre como sobreviver a Cálculo I</a>. Era um post de calouro recém-saído da prova final, escrito com a empolgação de quem tinha se apaixonado de vez por uma matéria que metade da turma jurou ser impossível. Dez anos depois, releio aquele post com afeto. O autor de vinte anos não tinha como saber, mas estava aprendendo o vocabulário básico de uma coisa que dez anos no futuro ia ser a infraestrutura silenciosa de tudo que ele usa pra trabalhar.</p>
<p>Nas últimas semanas escrevi por aqui sobre <a href="/blog/2026-05-29-30-dias-claude-code-usd-8k-plano-fixo/">os 30 dias que passei dentro do Claude Code</a>, rodando o equivalente a USD 8.069 em tokens e entregando quatro vezes mais que num mês normal. Escrevi também sobre <a href="/blog/2026-06-17-slash-command-save-session-claude-code/">construir um slash command pra não perder contexto entre sessões</a>, e sobre <a href="/blog/2026-06-01-clawtop-meu-primeiro-open-source/">o Clawtop, dashboard que montei pra acompanhar minha assinatura rodando em duas máquinas</a>. Tudo tratando IA como uma caixa preta sofisticada. Caixa que eu sei usar, que eu sei me beneficiar, que entrega leverage de outro patamar.</p>
<p>Esse post é o olhar por dentro. E o engraçado é que o que tem ali é exatamente o que eu amei estudar em Cálculo II e nunca imaginei rever na vida prática. As derivadas parciais (sonho com elas até hoje). Aquele negócio que eu achava lindo de um jeito abstrato: gradiente, jacobiana, todo um vocabulário que eu curtia pela elegância, sem fazer a menor ideia de onde ele ia desembocar dez anos depois.</p>
<p>Pois é. O que eu amava como abstração elegante era, o tempo todo, pura aplicação esperando uma década pra aparecer. Porque o gradiente descendente, ferramenta que nasce direto das derivadas parciais de Cálculo II, é literalmente o motor que treina toda IA moderna. Cada resposta que sai do Claude, do ChatGPT, de qualquer LLM que você usa hoje, foi possível porque alguém calculou bilhões de gradientes em algum momento do treinamento. Esse post é sobre como isso funciona, contado com a intuição que eu queria ter tido em 2015.</p>
<h2 id="o-alpinista-no-nevoeiro">O alpinista no nevoeiro</h2>
<p>Pra entender o gradiente descendente sem cair em fórmula formal, segura essa imagem. Você é um alpinista. Está na encosta de uma montanha, num lugar qualquer, e tem uma única intenção: descer até o vale. Tem só um problema. Você está num nevoeiro tão denso que não consegue enxergar nem o vale nem o pico. Não tem GPS. Não tem mapa. Os olhos não servem pra nada ali.</p>
<p>O que você tem é a sola do pé. Você sente o chão. Pode pressionar com a bota em direções diferentes e perceber em qual direção o terreno inclina pra baixo mais rapidamente. É um sinal sensorial, físico, simples. A decisão também é simples: você dá um passo nessa direção. Não dois, não vinte. Um passo, cuidadoso, na direção que parecia descer mais.</p>
<p>Depois do passo, você para. Reposiciona a bota. Sente o chão de novo. A direção pode ter mudado, porque o terreno é irregular e o ponto onde você está agora não é o mesmo de antes. Então você recalcula. Identifica de novo a direção em que o chão inclina pra baixo mais rápido. Dá outro passo. Para. Sente.</p>
<p>Faz isso por horas. No começo o terreno desce bem, você sente progresso óbvio, cada passo é claramente descida. Conforme você se aproxima de uma região mais plana, a inclinação fica menos pronunciada, os passos viram exploração mais hesitante. Eventualmente, em algum momento, o chão fica plano em todas as direções que você consegue sentir. Não tem mais pra onde descer. Você chegou em algum vale.</p>
<p>Sem mapa, sem visão. Só com o pé e com a paciência de repetir o gesto. Essa é a intuição inteira. Guarda ela.</p>
<h2 id="o-alpinista-é-a-matemática">O alpinista é a matemática</h2>
<p>Agora pega essa imagem e troca o vocabulário. O alpinista vira uma função matemática. A montanha vira a paisagem dessa função em algum espaço de coordenadas. A altura em cada ponto da montanha vira o valor da função nesse ponto. Você quer minimizar a função, o que significa querer chegar no ponto mais baixo, ou seja, no fundo do vale.</p>
<p>O ato de sentir o chão com o pé é literalmente o gradiente. O gradiente é o vetor formado pelas derivadas parciais da função em relação a cada coordenada. Cada componente desse vetor responde a uma pergunta simples e específica: se eu mexer só essa variável aqui, segurando todas as outras fixas, pra qual lado a função cresce mais rápido? Cada coordenada tem sua resposta. Quando você junta todas elas num vetor único, o vetor aponta na direção em que a função cresce mais rapidamente naquele ponto.</p>
<p>Como o alpinista quer descer, e não subir, ele caminha no sentido contrário ao gradiente. Pega o vetor que aponta pra cima e inverte a direção. Esse é o passo do gradiente descendente. Daí o nome do método, traduzido literalmente do inglês: gradient descent. Descer pelo gradiente.</p>
<p>E aqui reaparece, dez anos depois, aquela derivada de Cálculo I. Lembra dela? Aquela coisa de inclinação do gráfico, de tangente à curva, da régua imaginária encostada na curva pra medir o quão íngreme ela é naquele ponto. Pois é. Cada derivada parcial é exatamente isso, mas em uma única direção de cada vez, ignorando todas as outras. A derivada parcial em x mede inclinação no sentido de x. A derivada parcial em y mede inclinação no sentido de y. Junta as duas num vetor bidimensional e você tem o gradiente em um espaço de duas dimensões.</p>
<p>A sola do pé do alpinista é, literalmente, a coleção dessas derivadas parciais. Função objetivo virou montanha. Coordenadas viraram a posição na encosta. Derivada parcial virou sensor de inclinação por direção. Gradiente virou a bota sentindo o chão. Andar no sentido contrário ao gradiente virou o passo de descida. É a mesma matemática que você fez na prova, com nomes diferentes.</p>
<p>Pra cimentar: imagina uma função simples, uma parábola tridimensional que parece uma tigela. O fundo da tigela é o mínimo. Solta um alpinista em qualquer ponto da borda. A cada passo, ele sente o chão, calcula o gradiente, anda no sentido contrário. Em poucos passos, ele chega no fundo. Em duas dimensões, com uma tigela bem-comportada, é exercício de manhã de domingo.</p>
<p>Deixa eu sair da metáfora por dois minutos e mostrar a conta, porque ela é mais simples do que a fama sugere. Essa tigela é a função <code>f(x, y) = x² + y²</code>. O fundo dela fica na origem, no ponto <code>(0, 0)</code>, onde a função vale zero. É pra lá que o alpinista quer descer. A derivada parcial em <code>x</code> pergunta: segurando <code>y</code> fixo, como <code>f</code> muda quando eu mexo só em <code>x</code>? A resposta é <code>2x</code>. A derivada parcial em <code>y</code>, pela mesma lógica, é <code>2y</code>. O gradiente é só o par das duas junto: <code>∇f = (2x, 2y)</code>. Esse vetor aponta pra direção de subida mais rápida em cada ponto.</p>
<p>Agora solta o alpinista em <code>(3, 1)</code>, lá na borda. O gradiente nesse ponto é <code>(2·3, 2·1) = (6, 2)</code>, o lado pra cima. Como ele quer descer, inverte e caminha no sentido <code>(-6, -2)</code>. Mas não anda o vetor inteiro de uma vez, senão passa do fundo e sobe do outro lado. Multiplica por um passo pequeno, digamos <code>0,1</code>, e o deslocamento vira <code>(-0,6, -0,2)</code>. Sai de <code>(3, 1)</code> e chega em <code>(2,4, 0,8)</code>. Mais perto do fundo. No ponto novo recalcula o gradiente, <code>(4,8, 1,6)</code>, inverte, dá outro passo pequeno, e vai pra <code>(1,92, 0,64)</code>. E de novo. Cada passo encurta a distância até a origem. Repete umas dezenas de vezes e ele está praticamente no fundo da tigela. Sentir o chão é calcular <code>(2x, 2y)</code>. Dar o passo é subtrair uma fração disso. É a metáfora inteira virando aritmética de ensino médio.</p>
<p>E repara numa coisa, porque é o que eu queria ter entendido lá em 2015: nada disso aí em cima é cálculo sozinho. Ponto com coordenadas, vetor com direção e sentido, uma superfície desenhada num sistema de eixos. Isso é <strong>geometria analítica</strong> inteira. O gradiente só faz sentido porque é um vetor num espaço de coordenadas, e o passo do alpinista só é um passo porque a gente sabe somar um vetor a um ponto. O aluno que trata geometria analítica como a matéria chata que antecede o cálculo de verdade é exatamente o que, lá na frente, olha pro gradiente descendente e enxerga símbolo solto. Quem dedica tempo aos eixos, aos vetores, às superfícies, ganha o olho que transforma fórmula em paisagem. Sem essa base, derivada parcial vira manipulação cega de letra. Com ela, vira o alpinista sentindo o chão.</p>
<p>A graça começa quando a função não é uma tigela limpa. É uma cordilheira com vales múltiplos, picos secundários, planaltos longos, falhas geológicas. O alpinista no nevoeiro ainda consegue descer, mas pode parar num vale local que não é o mais fundo da paisagem. Esse é o problema fundamental do método. Mas mesmo limitado, ele continua descendo, e em muitos casos o vale local é bom o suficiente pro que se quer fazer.</p>
<h2 id="bilhões-de-passos-no-nevoeiro">Bilhões de passos no nevoeiro</h2>
<p>Agora cola tudo. Uma rede neural é uma função matemática descomunalmente grande. Tem bilhões de parâmetros, e cada parâmetro é uma coordenada num espaço de bilhões de dimensões. A função associa esses parâmetros a uma medida de erro: dado um conjunto de entradas (texto, imagem, código, o que for) e o que a rede produziu como resposta, o quão longe ela está do que se esperava? Quanto menor o erro, melhor a rede. Treinar a rede é encontrar os parâmetros que minimizam essa função de erro.</p>
<p>Treinar é exatamente o que o alpinista faz, mas em escala que beira o absurdo. Os pesos iniciais da rede são aleatórios, o que equivale a largar o alpinista em qualquer ponto dessa paisagem com bilhões de dimensões. A função de erro é a altura nessa paisagem, e o objetivo é descer. A cada lote de dados que entra, calcula-se o gradiente dessa função de erro em relação a cada um dos bilhões de parâmetros. Inverte o vetor. Dá um pequeno passo na direção oposta. Recalcula. Dá outro passo.</p>
<p>Faz isso milhões, bilhões de vezes. Cada passo é minúsculo, quase imperceptível na escala do total. Mas a paisagem é tão grande que a soma dos passos importa. Em algumas semanas de treinamento, em milhares de GPUs rodando em paralelo, um modelo grande converge pra algum vale dessa paisagem. Não necessariamente o vale mais fundo possível, porque o alpinista no nevoeiro não sabe diferenciar vale local de vale global em uma paisagem de bilhões de dimensões. Mas algum vale onde o erro é baixo o suficiente pra que a rede responda bem em cima de dados que ela nunca viu antes.</p>
<p>Aqui mora a piada que sustenta todo o restante. Toda vez que você faz uma pergunta pro Claude, pro ChatGPT, pra qualquer LLM moderno, a resposta que aparece é o resultado acumulado de bilhões e bilhões de passos desse alpinista no nevoeiro, dados em alguma paisagem com bilhões de dimensões que ninguém vê inteira. O modelo é o alpinista no final da jornada, parado num vale, agora competente em responder a partir do que aprendeu descendo. Você está conversando com o resultado de uma escalada às escuras.</p>
<p>Aquele USD 8.069 em tokens que eu rodei em 30 dias dentro do Claude Code, traduzido em quatro vezes mais entrega no roadmap, não é mágica nem algo misterioso. É o resultado de muito alpinismo no nevoeiro, que aconteceu uma vez durante o treinamento do modelo, e que agora é convocado em outra forma toda vez que eu mando uma mensagem pra ele. O ato de gerar a resposta (a inferência) usa a paisagem já treinada, mas a paisagem em si foi construída por bilhões de passos do alpinista no nevoeiro descendo a montanha do erro durante semanas, em milhares de máquinas que eu nunca vi.</p>
<p>E aqui amarra com 2015. Muito aluno atravessa derivadas parciais achando que é abstração de prova e larga assim que entrega a avaliação. Eu não fui esse aluno: eu amei aquilo. Mas amar a matéria não me deu o que faltava, a noção de onde ela ia desembocar. Dez anos depois, descobri que a elegância que eu curtia no quadro era a infraestrutura silenciosa do que ia me dar quatro vezes mais entrega no mesmo mês. A parte mais engraçada é que a essência continua a mesma coisa simples que era no laboratório de Cálculo. Sentir o chão. Dar um passo. Sentir de novo. Repetir.</p>
<p>Se você está na faculdade hoje, atravessando <strong>Cálculo II</strong> com a sensação de que nunca mais vai usar aquilo, peço atenção. Você está aprendendo a base do que sustenta a revolução tecnológica do nosso tempo. Talvez você acabe se beneficiando dela sem nunca fazer a conexão. Mas se um dia você decidir olhar pra dentro da caixa preta, vai encontrar a derivada parcial de Cálculo II do mesmo jeito que ela ficou na sua memória. Sentindo o chão. Dando um passo. Sentindo de novo.</p>
<h2 id="conclusão">Conclusão</h2>
<p>Comecei essa minissérie tratando IA como caixa preta: os 30 dias dentro do Claude Code, o slash command pra não perder contexto, o Clawtop monitorando a assinatura em duas máquinas. Tudo da porta pra fora, eu operando a ferramenta sem precisar saber o que tinha dentro. Esse post foi abrir a porta. E o que tem lá dentro não é nenhuma feitiçaria nova: é a derivada parcial que eu calculei na prova de Cálculo II, repetida bilhões de vezes numa paisagem grande demais pra qualquer um enxergar inteira.</p>
<p>Acho que é essa a parte que eu queria ter sacado em 2015. Mas o primeiro lampejo veio antes do que esse arco de dez anos deixa parecer. Já em 2016, um ano depois daquele post de calouro, tive meu primeiro contato sério com machine learning e redes neurais, e ali eu já enxerguei muita coisa dos estudos de Cálculo e principalmente de geometria analítica encontrando aplicação direta, sem abstração nenhuma. Não era o entendimento que tenho hoje, mas foi a primeira vez que o vocabulário do quadro me mostrou que tinha endereço no mundo real. O que faltava era viver isso na pele, e foi o que esses 30 dias dentro do Claude Code finalmente fizeram. A distância entre a abstração elegante do quadro e a coisa que move o mundo é menor do que parece. Não tem um salto mágico no meio, tem acúmulo. Bilhões de passos pequenos numa direção, cada um quase imperceptível, somando até virar uma montanha descida. Vale pra rede neural e vale, sem forçar a metáfora, pra carreira de quem decide aprender a base direito em vez de decorar pra passar.</p>
<p>Quando você manda a próxima mensagem pro Claude e a resposta aparece em segundos, lembra do alpinista no nevoeiro. Você não está conversando com mágica. Está conversando com o resultado de uma escalada às escuras, montada em cima da mesma matemática que um dia te pareceu abstrata demais pra servir pra alguma coisa.</p>
]]></content:encoded><category>matemática</category><category>ai</category><category>machine-learning</category><category>claude-code</category><category>educação</category></item><item><title>Slash commands no Claude Code: como construí o /save-session e por que ele virou ritual de fim de sessão</title><link>https://rifeli.dev/blog/2026-06-17-slash-command-save-session-claude-code/</link><pubDate>Wed, 17 Jun 2026 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/2026-06-17-slash-command-save-session-claude-code/</guid><description>Tutorial prático de slash command custom no Claude Code, com o caso real do /save-session. Por que precisei criar, como funciona o auto-memory que o Claude Code já dá de fábrica, o que o /save-session adiciona, e como compartilhar o skill entre múltiplas máquinas via dotfiles.</description><content:encoded><![CDATA[<img id="image-custom" src="/images/posts/e2cad8ae-006f-4dfc-970f-107334842be9.png" alt="Um terminal enviando camadas de dados para um núcleo de memória, que devolve o contexto para um desktop e um notebook" />
<p id="image-legend">Um comando no fim da sessão: o contexto que importa vira memória, sincronizada entre as máquinas.</p>
<h2 id="introdução">Introdução</h2>
<p>Em pouco mais de um mês rodando dentro do Claude Code, em duas máquinas, atravessei 180 sessões. Em todas elas, no fechamento, o contexto morria. As decisões, as preferências reforçadas, os &ldquo;não faz mais X assim&rdquo; e os &ldquo;esse padrão funcionou, repete depois&rdquo; iam todos pro lixo. Na sessão seguinte, eu re-explicava.</p>
<p>Em um post anterior dessa série, sobre os <a href="/blog/2026-05-29-30-dias-claude-code-usd-8k-plano-fixo/">30 dias dentro do Claude Code</a>, deixei pendente a história do slash command que entrou pra resolver esse problema. Esse aqui é ele. Te conto a necessidade que me fez construir, como funciona, e te ensino a montar o seu próprio. No fim, falo de compartilhar entre máquinas, que foi a peça que destravou pra valer.</p>
<h2 id="a-necessidade">A necessidade</h2>
<p>O Claude Code já tem um sistema de memória persistente que roda no fundo. É o que a Anthropic chama de auto-memory: durante a conversa, o modelo pode decidir salvar fatos relevantes em arquivos no diretório <code>~/.claude/projects/&lt;encoded-cwd&gt;/memory/</code>. Esses arquivos viram contexto carregado em conversas futuras no mesmo projeto. É bom, é automático, e cobre o caso óbvio.</p>
<p>O problema é a passividade. O modelo só salva quando &ldquo;decide&rdquo; que algo é digno. Coisas que pra mim eram óbvias de carregar (uma decisão de arquitetura específica, uma preferência de escrita validada, etc) ele às vezes salvava, às vezes não. E quando eu queria forçar, ficava digitando &ldquo;salva isso na sua memória&rdquo; no fim da sessão, sentindo como se estivesse instruindo um estagiário em três etapas.</p>
<p>A necessidade era simples: um comando que eu invocasse explicitamente no fim de qualquer sessão, com o entendimento de que naquele momento eu <em>quero</em> salvar, e o bar de &ldquo;vale a pena guardar&rdquo; pode ser mais baixo do que o automático. Esse é o <code>/save-session</code>.</p>
<h2 id="o-que-é-um-slash-command-no-claude-code">O que é um slash command no Claude Code</h2>
<p>Antes de ir pro código, contexto pra quem nunca construiu. Slash commands no Claude Code são skills custom que você invoca digitando <code>/&lt;nome&gt;</code> na conversa. O Claude detecta o comando, carrega as instruções daquele skill, e executa o procedimento descrito. Skills vivem em duas hierarquias possíveis:</p>
<ul>
<li><code>~/.claude/skills/&lt;nome&gt;/SKILL.md</code>: disponível em todas as conversas, em todas as pastas do seu sistema. (global no PC)</li>
<li><code>&lt;projeto&gt;/.claude/skills/&lt;nome&gt;/SKILL.md</code>: disponível só quando o Claude Code abre dentro daquele projeto. (somente em projeto específico)</li>
</ul>
<p>Pra um comando utilitário como <code>/save-session</code>, que faz sentido em qualquer sessão, a hierarquia certa é a global em <code>~/.claude/skills/</code>.</p>
<p>A estrutura mínima de um <strong>SKILL.md</strong> é frontmatter YAML mais corpo em markdown. O frontmatter declara o comando, o que ele faz e como o usuário interage. O corpo é o procedimento: a instrução em linguagem natural que o Claude vai executar quando você invocar.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-markdown" data-lang="markdown"><span style="display:flex;"><span>---
</span></span><span style="display:flex;"><span>name: meu-comando
</span></span><span style="display:flex;"><span>description: O que ele faz, em uma frase
</span></span><span style="display:flex;"><span>argument-hint: [opcional, ajuda de argumento que aparece no prompt]
</span></span><span style="display:flex;"><span>disable-model-invocation: true
</span></span><span style="display:flex;"><span>---
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="font-weight:bold"># meu-comando
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Instruções em prosa do que o Claude deve fazer quando o usuário digitar /meu-comando.
</span></span></code></pre></div><p>O campo <code>disable-model-invocation: true</code> é importante: ele impede que o modelo invoque seu skill por conta própria sem o usuário ter digitado o comando. Pra um skill com efeito colateral (escrita de arquivo, leitura de memória, qualquer coisa que altera estado), isso é essencial.</p>
<h2 id="anatomia-do-save-session">Anatomia do /save-session</h2>
<p>O SKILL.md inteiro do <code>/save-session</code> tem cerca de 50 linhas. Vou mostrar o esqueleto e comentar as decisões.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-markdown" data-lang="markdown"><span style="display:flex;"><span>---
</span></span><span style="display:flex;"><span>name: save-session
</span></span><span style="display:flex;"><span>description: Extract durable, non-obvious learnings from the current conversation and persist them to the project memory system. Filters out code, ephemeral numbers, and anything derivable from the repo or git history. Use when the user wants to &#34;save&#34;, &#34;remember&#34;, or &#34;salvar na memória&#34; the current session, or just runs <span style="color:#f1fa8c">`/save-session`</span>.
</span></span><span style="display:flex;"><span>argument-hint: [optional scope hint, e.g. &#34;só sobre Redis&#34;]
</span></span><span style="display:flex;"><span>disable-model-invocation: true
</span></span><span style="display:flex;"><span>---
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="font-weight:bold"># save-session
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Triggered explicitly by the user to capture what&#39;s worth carrying into future conversations.
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="font-weight:bold">## Procedure
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">1.</span> Locate the memory directory at <span style="color:#f1fa8c">`~/.claude/projects/&lt;encoded-cwd&gt;/memory/`</span>.
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">2.</span> Read existing memories first. Update or remove before writing new ones.
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">3.</span> Review the conversation for four memory types: user, feedback, project, reference.
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">4.</span> Apply filters: do NOT save code patterns, file paths, ephemeral numbers.
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">5.</span> Honour the scope hint if the user passed one.
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">6.</span> Write the files with frontmatter (name, description, type) plus body.
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">7.</span> Update MEMORY.md as an index (one line per memory, under 150 chars).
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">8.</span> Report back: created, updated, skipped.
</span></span></code></pre></div><p>Três decisões valem comentar.</p>
<p>A primeira é o <strong>description em inglês cobrindo aliases naturais em PT-BR</strong>: &ldquo;salvar na memória&rdquo;, &ldquo;remember&rdquo;, &ldquo;save the session&rdquo;. Quando o modelo é treinado nativamente em inglês mas a conversa pode estar em português, o description que abraça os dois ajuda na detecção do trigger.</p>
<p>A segunda é o <strong>argument-hint</strong>. Você pode passar <code>/save-session só sobre Redis</code> e o skill respeita esse escopo, filtrando o que extrai. Sem isso, fica tudo-ou-nada.</p>
<p>A terceira é a <strong>disciplina de filtros explícitos no procedure</strong>. O auto-memory também filtra, mas o <code>/save-session</code> adiciona pressão: &ldquo;do NOT save code patterns, file paths, ephemeral numbers&rdquo;. O bar de relevância sobe, porque o usuário pediu, então o que entra precisa valer a pena ser carregado em todas as conversas futuras desse projeto.</p>
<h2 id="como-ele-funciona-em-uma-sessão-real">Como ele funciona em uma sessão real</h2>
<p>Quando você digita <code>/save-session</code> no fim de uma conversa, o Claude faz quatro coisas em ordem:</p>
<p>Primeiro, descobre o diretório de memória do projeto atual (<code>~/.claude/projects/&lt;encoded-cwd&gt;/memory/</code>) e lê o <code>MEMORY.md</code> mais os arquivos de memória existentes. Isso é regra mandatória, porque a primeira coisa que o skill precisa garantir é que ele não vai duplicar nem reescrever memória já registrada.</p>
<p>Segundo, revisa a conversa em busca de candidatos divididos em quatro tipos: <strong>user</strong> (perfil, expertise, perspectiva), <strong>feedback</strong> (correções e validações com o <code>Why</code> e o <code>How to apply</code>), <strong>project</strong> (estado de trabalho com <code>Why</code> e <code>How to apply</code>), e <strong>reference</strong> (apontadores pra sistemas externos, URLs, hostnames).</p>
<p>Terceiro, aplica os filtros. Coisas que estão no código não viram memória, porque o código é a fonte da verdade. Datas relativas viram absolutas. Números financeiros voláteis (vão mudar em semanas) ficam de fora.</p>
<p>Quarto, escreve os arquivos novos, atualiza os existentes que precisam, e atualiza o índice <code>MEMORY.md</code>. No fim, reporta o que criou, o que mexeu, o que deixou de fora.</p>
<p>O efeito prático é que a próxima conversa que eu abrir nesse projeto vai carregar como contexto inicial todas as preferências, decisões e referências que valeram a pena. Sem precisar re-explicar. Sem precisar copiar e colar texto antigo. O modelo me trata como alguém que ele já conhece.</p>
<h2 id="compartilhando-o-skill-entre-máquinas">Compartilhando o skill entre máquinas</h2>
<p>Eu rodo Claude Code em duas máquinas ativas, desktop e notebook, conforme já contei no <a href="/blog/2026-05-29-30-dias-claude-code-usd-8k-plano-fixo/">post sobre 30 dias dentro do Claude Code</a>. Se eu criasse o skill só em uma delas, perderia metade do benefício.</p>
<p>A solução foi simples: o SKILL.md vive no meu repositório de <strong>dotfiles</strong> versionado, em <code>~/projects/personal/dotfiles/.claude/skills/save-session/SKILL.md</code>. Aí, em cada máquina, eu rodo um stow ou um script de symlink que conecta <code>~/.claude/skills/save-session</code> ao arquivo dentro de dotfiles.</p>
<p>Na prática, vira algo assim:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#6272a4"># uma vez por máquina, no setup</span>
</span></span><span style="display:flex;"><span>mkdir -p ~/.claude/skills
</span></span><span style="display:flex;"><span>ln -s ~/projects/personal/dotfiles/.claude/skills/save-session ~/.claude/skills/save-session
</span></span></code></pre></div><p>Resultado: editar o skill em uma máquina, comitar no repo de dotfiles, dar pull na outra, e o <code>/save-session</code> está disponível com a mesma definição em todos os lugares. Mesma lógica vale pra outros skills que eu desenvolvi (scripts de stats, hooks de diagnóstico, custom commands de fluxo). Todos centralizados, todos versionados, todos sincronizados.</p>
<p>Esse é o passo que muita gente esquece: skill é código, e código você versiona. Se você está construindo um slash command que vai mexer com memória, com arquivos, com qualquer estado real, vale tratá-lo com a mesma seriedade que você trata qualquer parte da sua ferramentaria.</p>
<h2 id="cinco-aprendizados-do-uso">Cinco aprendizados do uso</h2>
<ul>
<li><strong>Bar de &ldquo;vale salvar&rdquo; deve ser explícito no skill, não implícito</strong>. Auto-memory deixa o bar implícito no julgamento do modelo. Slash command explícito permite codificar regras de inclusão/exclusão. Use isso.</li>
<li><strong>Memória boa é estruturada por tipo, não por ordem cronológica</strong>. Separe user, feedback, project, reference. Quando você for buscar algo em três meses, vai querer encontrar por categoria, não por &ldquo;quando aconteceu&rdquo;.</li>
<li><strong>Índice (<code>MEMORY.md</code>) precisa ser índice</strong>. Uma linha por memória, descrição curta. Não escreve conteúdo no índice. Conteúdo vai pros arquivos individuais. Senão você acaba com um único arquivo gigante que o modelo ignora por sobrecarga de contexto.</li>
<li><strong>Salve o &ldquo;Why&rdquo;, não só o &ldquo;What&rdquo;</strong>. Uma feedback memory que diz &ldquo;evitar emoji em posts&rdquo; é fraca. Uma que diz &ldquo;evitar emoji em posts porque o público é C-level sênior e emoji destrói credibilidade na primeira leitura&rdquo; é forte. O <code>Why</code> permite julgamento em casos novos.</li>
<li><strong>Versione o skill em dotfiles desde o dia um</strong>. Antes mesmo de ele ficar bom. Você vai iterar, e cada iteração precisa estar visível em todas as máquinas. Skill que mora em uma máquina só é skill que vai morrer no próximo backup que você esquecer de fazer.</li>
</ul>
<h2 id="fechamento">Fechamento</h2>
<p>O <code>/save-session</code> virou ritual de fechamento de cada sessão de trabalho importante. Antes de fechar o Claude Code, eu digito ele. Levam uns 20 a 40 segundos, e o que entra em memória ali volta na próxima sessão como contexto pronto, sem que eu precise pensar.</p>
<p>Construir slash commands custom no Claude Code é a fronteira que separa &ldquo;uso a ferramenta&rdquo; de &ldquo;operacionalizo a ferramenta&rdquo;. Auto-memory te dá o caminho fácil. Skills custom te dão a alavanca: você codifica seu próprio processo dentro do agente, e ele passa a executar do seu jeito, com seus filtros, com seus padrões.</p>
<p>Se você usa Claude Code com regularidade e ainda não tem nenhum skill custom rodando, esse é o ponto natural pra começar. Não precisa ser save-session. Pode ser qualquer ritual repetitivo que você executa hoje à mão. Codifica ele em um SKILL.md, joga no dotfiles, symlinka pra <code>~/.claude/skills/</code>, e a partir da próxima sessão é só digitar <code>/</code>.</p>
]]></content:encoded><category>claude-code</category><category>ai</category><category>dev-tools</category><category>tutorial</category><category>anthropic</category></item><item><title>Seis anos de Harmo: o que aprendi como CTO construindo infraestrutura Drive-to-Store</title><link>https://rifeli.dev/blog/2026-06-08-seis-anos-harmo-cto-infraestrutura-drive-to-store/</link><pubDate>Mon, 08 Jun 2026 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/2026-06-08-seis-anos-harmo-cto-infraestrutura-drive-to-store/</guid><description>A Harmo fez seis anos. Escrevo como co-founder e CTO sobre o que custou caro construir: tratar pilares Drive-to-Store como um sistema único em vez de quatro produtos costurados, processar +300 mil avaliações e +10 milhões de pesquisas por mês em mais de +60 mil lojas, e por que time e arquitetura são a mesma coisa. Decisões técnicas, lições de gestão e o diferencial que só aparece quando você constrói infraestrutura/plataforma, não feature.</description><content:encoded><![CDATA[<img id="image-custom" src="/images/posts/f4160481-ac27-4807-a036-e04c1b394436.png" alt="Quatro canais de comunicação convergindo por trilhas luminosas até um ponto único que aponta para uma loja física" />
<p id="image-legend">6 anos como CTO de uma infraestrutura Drive-to-Store</p>
<h2 id="introdução">Introdução</h2>
<p>A Harmo fez seis anos no mês de junho. Esse tipo de data costuma virar texto de marketing, mas eu queria escrever de outro lugar: o de quem assina o código e responde pelo time que o mantém. Então esquece o tom de release. Quero falar do que de fato custou caro construir, das decisões de arquitetura que sustentaram os outros cinco anos, e das lições de gestão que eu só aprendi errando.</p>
<p>A pergunta que a gente fazia no começo era simples e a resposta era difícil: por que a voz do cliente, que é o dado mais valioso que uma rede de varejo tem, vivia espalhada em ferramentas que não conversavam entre si? Avaliação num lugar, presença digital em outro, pesquisa em outro. Cada pedaço resolvido por uma solução isolada, nenhuma resolvendo o problema inteiro. A Harmo nasceu dessa inconformidade. Seis anos depois, ela não é uma ferramenta de GBP, não é uma plataforma de reputação e não é uma agência. É infraestrutura Drive-to-Store: um único sistema, integrado e governado, que transforma a voz do cliente em mais fluxo para a loja.</p>
<h2 id="o-problema-técnico-nunca-foi-o-óbvio">O problema técnico nunca foi o óbvio</h2>
<p>De fora parece que o desafio é integrar APIs e mostrar avaliação num dash bonito. Não é. O desafio real é manter um ciclo contínuo rodando em escala, com dados que chegam o tempo todo, de fontes que mudam as regras sem avisar, sobre uma base que hoje passa de 60 mil lojas. Processar mais de 300 mil avaliações e mais de 10 milhões de pesquisas por mês não é número de dashboard, é problema de arquitetura de verdade, com tudo que isso implica: idempotência, deduplicação, reprocessamento, e a disciplina chata de tratar fonte externa como algo que vai falhar, mudar de contrato e te surpreender no pior dia.</p>
<p>A decisão que sustentou todo o resto foi tratar pilares Drive-to-Store como um sistema único, não como quatro produtos costurados depois. Isso significou pagar adiantado em complexidade de modelagem de dados para que cada avaliação, cada pesquisa e cada ponto de presença digital alimentassem o mesmo ciclo, com o mesmo identificador de loja, o mesmo conceito de evento, a mesma fonte de verdade. Foi mais difícil no começo e é exatamente o que hoje nos deixa entregar coisas que um amontoado de ferramentas isoladas nunca vai entregar. Inteligência de verdade não se pluga em dados desconectados.</p>
<p>Boa parte do que sustenta isso na prática é matemática: estatística, correlação e os modelos que ligam a nota da loja ao fluxo na porta. Já mostrei o lado de dados e Python disso em <a href="/blog/python-ia-dados-harmo-basico-ao-pratico/">como começamos com Python para IA e dados na Harmo</a>, e em breve vou dedicar um post inteiro às oito famílias de fórmulas que rodam por trás da plataforma. Do lado da entrega, já escrevi sobre <a href="/blog/como-fazemos-ci-cd-na-harmo/">como fazemos CI/CD na Harmo</a>. Este post aqui é mais sobre as escolhas que vieram antes do código.</p>
<h2 id="time-é-arquitetura">Time é arquitetura</h2>
<p>A lição de gestão que eu mais carrego é que time e arquitetura são a mesma coisa. O sistema que você consegue construir é o sistema que o seu time consegue sustentar. A Lei de Conway não é uma curiosidade acadêmica, é uma profecia que se cumpre todo trimestre. Por seis anos a prioridade não foi contratar rápido, foi contratar gente que entendesse que estávamos construindo infraestrutura, não feature. São coisas diferentes. Feature você entrega e comemora. Infraestrutura você entrega e passa a sustentar para sempre, e a régua de qualidade é outra porque o custo de errar se acumula em vez de passar.</p>
<p>A parte mais difícil como CTO foi aprender a dizer não para o caminho fácil. Toda vez que aparecia o atalho de resolver um caso pontual com uma gambiarra que não cabia no ciclo, a resposta certa quase sempre era a mais cara no curto prazo. Manter a coerência do sistema acima da velocidade de uma entrega isolada é uma disciplina que custa caro e some das métricas de curto prazo, e é exatamente ela que separa uma plataforma de uma colcha de retalhos. Eu errei isso algumas vezes nos primeiros anos. Cada vez que cedi ao atalho, paguei depois, com juros, em reescrita.</p>
<p>A outra lição é sobre dívida técnica como decisão de negócio, não como pecado. Nem toda dívida é ruim. A dívida que você contrai consciente, anota e cobra de volta no prazo é alavanca. A que você contrai sem perceber, espalhada em mil decisões pequenas que ninguém documentou, é a que te quebra. O trabalho do líder técnico é separar uma da outra e ter a coragem de defender a primeira na frente do board.</p>
<h2 id="o-diferencial-não-é-uma-funcionalidade-é-o-sistema">O diferencial não é uma funcionalidade, é o sistema</h2>
<p>Quando comparam a Harmo com o mercado, eu gosto de mostrar um número que diz muito: nas oito funcionalidades centrais de GBP, a Harmo cobre as oito. É o tipo de cobertura que só faz sentido quando você construiu infraestrutura, não quando empilhou recursos um sobre o outro. Cada funcionalidade isolada é fácil de copiar. O que é difícil de copiar é a integração que faz as oito conversarem entre si sobre a mesma base de dados.</p>
<p>E o salto dos últimos anos foi a FloraAI, nossa camada de inteligência transversal, já lançada e disponível em todos os planos. Ela não é um módulo à parte que vendemos como add-on, é inteligência rodando por cima de todo o ciclo. Isso só foi possível porque os dados já estavam integrados desde o primeiro dia. A FloraAI é, no fundo, a colheita de uma decisão de arquitetura que tomamos anos antes de existir hype de IA. A gente não construiu a fundação porque sabia que a IA viria assim. A gente construiu a fundação porque dado integrado sempre vale mais que dado disperso, e isso é verdade independente de moda.</p>
<h2 id="reputação-é-performance-e-isso-é-mensurável">Reputação é performance, e isso é mensurável</h2>
<p>O que mantém tudo isso de pé não é estética, é resultado. Reputação não é imagem, é performance. A correlação que orienta nosso trabalho mostra isso de forma quase brutal: cada 0,1 estrela a mais se traduz em 8,8% a mais em pedidos de rota. Na prática, ao longo de doze meses, isso aparece como um aumento de 36% nos pedidos de rota para a loja. Quando você consegue ligar uma variável que o time de operação enxerga como subjetiva, a nota, a um número que o CFO enxerga como concreto, gente entrando na loja, a conversa muda de departamento.</p>
<p>É por isso que insisto que a voz do cliente é dado estratégico, não SAC. Todo o trabalho técnico, do pipeline de ingestão à modelagem, do CI/CD à FloraAI, existe para uma coisa só: transformar esse dado em fluxo na porta da loja, de forma mensurável e auditável. Se eu não consigo provar o impacto com número, é estética, e estética não é o que a gente vende.</p>
<h2 id="o-que-vem-agora">O que vem agora</h2>
<p>Seis anos depois, com mais de 200 clientes confiando na plataforma, eu olho menos para o que construímos e mais para o que essa fundação permite construir agora. Infraestrutura boa é assim: o valor dela não está no que ela já faz, está no que ela torna possível fazer a seguir. A FloraAI é o primeiro grande exemplo disso, e tenho convicção de que não vai ser o último.</p>
<p>Obrigado a todo mundo que escreveu uma linha desse sistema, abriu um chamado às três da manhã, ou brigou comigo numa revisão de arquitetura porque tinha um jeito melhor de fazer. Infraestrutura é coisa de time, e esse time é a melhor coisa que construímos em seis anos. Vamos para o sétimo.</p>
]]></content:encoded><category>harmo</category><category>arquitetura</category><category>gestão</category><category>carreira</category><category>drive-to-store</category></item><item><title>O loop improdutivo que multiplicou nossa conta AWS por quase 6x em quatro madrugadas</title><link>https://rifeli.dev/blog/2026-06-05-loop-improdutivo-step-functions-6x-aws/</link><pubDate>Fri, 05 Jun 2026 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/2026-06-05-loop-improdutivo-step-functions-6x-aws/</guid><description>Case real de um incidente AWS: uma regressão sutil em uma lambda fez uma Step Function reinvocar a mesma tarefa por quatro madrugadas. A conta do mês saltou quase 6x (mesmo com AWS Cost Anomaly Detection habilitado). Como contivemos, como negociamos a concessão com a AWS via Infomach e TD Synnex, e quais guard rails entraram em pé. Case público para que outros times não descubram essa categoria de falha do mesmo jeito que a gente descobriu.</description><content:encoded><![CDATA[<img id="image-custom" src="/images/posts/8114554f-af51-4928-932f-a2bb01af2c38.png" alt="Diagrama em neon de uma função Lambda da AWS presa num laço que se realimenta, com cifrões escapando do ciclo" />
<p id="image-legend">Custo AWS mensal: pico de março com fator próximo de 6x sobre a baseline histórica.</p>
<h2 id="introdução">Introdução</h2>
<p>Na manhã de uma segunda-feira de março, abri o dashboard de billing como de hábito e a conta AWS do mês estava quase 6x acima da baseline histórica. Nenhum alarme técnico tinha disparado. Zero impacto pra usuário final, zero perda de dado, zero degradação em serviços vizinhos. Mas o billing sangrou por 76 horas seguidas.</p>
<p>A Harmo é uma Plataforma Drive-to-Store que processa +10 milhões de pesquisas e +300 mil avaliações públicas por mês. Cada um desses números toca uma família de fluxos serverless. Quando um único fluxo entra em loop improdutivo, ele paga o preço pelo restante.</p>
<p>Case público é prática consolidada na engenharia séria de software, e blameless é o nosso padrão interno. Resolvi escrever sobre esse case, porque a topologia da falha é generalizável: quem opera Step Functions, máquinas de estado ou qualquer fluxo com loop pode tropeçar na mesma armadilha. Foi o único evento desse perfil no histórico do ambiente, e a categoria de falha não tem mais como acontecer depois das contramedidas que descrevo abaixo.</p>
<h2 id="a-topologia-do-fluxo">A topologia do fluxo</h2>
<p>O fluxo afetado é uma Step Function de coleta paginada de dados via API externa, agendada para rodar de madrugada. O desenho é trivial em pseudocódigo:</p>
<pre tabindex="0"><code>[Start] → [Invoke Lambda] → [Choice: isFinished?]
                              ├── true  → [Persist] → [End]
                              └── false → [Invoke Lambda]   (loop)
</code></pre><p>Um Choice state que retorna pra invocação da lambda enquanto <code>$.collector.isFinished == false</code>. Em operação normal, a lambda esgota a paginação da API de origem em 3 a 8 invocações por execução. Limpinho, funcionou estável por anos.</p>
<h2 id="a-regressão">A regressão</h2>
<p>Numa quinta-feira, 19h05, mergeei um PR que mudou a lógica de paginação dessa lambda. Passou no review, passou nos testes existentes, passou na esteira. Não violava o contrato de I/O da função, todos os campos do payload continuavam com o shape esperado, tipos corretos, schema válido.</p>
<p>Sob uma condição específica de entrada (combinação rara de filtros com janela temporal), a lambda passou a retornar o seguinte payload <strong>a cada invocação</strong>, idêntico, sem variar entre chamadas consecutivas da mesma execução:</p>
<table>
  <thead>
      <tr>
          <th>Campo</th>
          <th>Valor</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>cursor</code></td>
          <td>0</td>
      </tr>
      <tr>
          <td><code>buffer</code></td>
          <td>0</td>
      </tr>
      <tr>
          <td><code>isFinished</code></td>
          <td><strong>false</strong></td>
      </tr>
      <tr>
          <td><code>totalFiltered</code></td>
          <td>0</td>
      </tr>
      <tr>
          <td><code>totalFetched</code></td>
          <td>0</td>
      </tr>
      <tr>
          <td><code>pageCount</code></td>
          <td>1</td>
      </tr>
  </tbody>
</table>
<p>Todos os sinais disponíveis indicavam ausência total de progresso: cursor não avança, nada acumulado, nada trazido, primeira página perpétua, e a chave de saída em <code>false</code>. Mas o payload era HTTP 200, bem-formado, schema-válido. Do ponto de vista do Choice state da Step Function, era instrução pra continuar. Ele continuou.</p>
<h2 id="as-quatro-madrugadas">As quatro madrugadas</h2>
<p>Cada execução afetada consumia o teto de 25.000 eventos do histórico da Step Function antes de falhar com um erro <code>States.Runtime</code> cuja causa era literalmente <code>The execution reached the maximum number of history events (25000).</code>. Eram cerca de 8 horas e meia de loop, com a Step Function consumindo invocações Lambda (por 15min) e transições SFN em ritmo frenético até bater no limite hard da AWS. Esse teto de 25.000 eventos é quota hard de Standard Workflow, não dá pra aumentar via Service Quotas \o/.</p>
<p>O padrão recorreu em quatro madrugadas consecutivas, atravessando um final de semana inteiro. No total, 76 horas de recorrência intermitente em que cada ciclo entregava <code>States.Runtime</code>.</p>
<h2 id="por-que-nada-disparou">Por que nada disparou</h2>
<p>Três camadas falharam ao mesmo tempo, e cada uma vale comentar.</p>
<p><strong>Camada de código.</strong> Os testes unitários da lambda validavam o shape do payload retornado. O bug não violou nenhum shape, retornou valores logicamente inconsistentes com tipos válidos. Teste de shape passa, problema de invariância não pega. Não havia teste validando &ldquo;se <code>isFinished=false</code>, então deve haver evidência de progresso entre invocações&rdquo;.</p>
<p><strong>Camada de arquitetura.</strong> O Choice state da Step Function fazia inspeção puramente sintática de uma única chave: <code>isFinished</code>. Não havia comparação entre estado antes e depois da invocação, nem teto de iterações. Do ponto de vista da Step Function, o sistema estava se comportando corretamente: recebeu instrução pra continuar, continuou.</p>
<p><strong>Camada de observabilidade.</strong> Não havia alarme sobre duração de execução de Step Function, sobre número de transições por execução, nem sobre concorrência da lambda contra baseline rolling. A única linha de defesa era o sinal financeiro, que por design tem latência de consolidação de 24 a 48 horas. O Cost Anomaly Detection da AWS requer múltiplos pontos de observação pra estabelecer baseline antes de disparar. Um único evento anômalo pode não ultrapassar o threshold estatístico nos primeiros dias, foi o que aconteceu.</p>
<p>Toda invocação retornou 200. Nenhum erro, nenhuma exceção, nenhum stack trace. O sistema estava funcionando, no sentido mais raso da palavra.</p>
<h2 id="a-detecção-e-a-contenção">A detecção e a contenção</h2>
<p>Trinta de março, segunda-feira, ~9h. Anomalia de custo identificada na revisão matinal do dashboard de billing. Em 15 minutos de investigação, correlacionei invocações da lambda com transições da Step Function, vi a assinatura do loop, conferi os payloads. Trinta minutos depois do disparo, o fluxo estava contido. No mesmo dia, root cause analysis concluída, correção preparada, deploy com validação ativa.</p>
<p>A partir de 31 de março, monitoramento intensificado, zero recorrência. Abril fechou exatamente na baseline histórica do ambiente. A correção foi efetiva e não havia dívida oculta.</p>
<p>Todo o processo, contamos com a parceria de anos com a <a href="https://www.infomach.com.br/">Infomach</a>, o que foi primordial para um bom sucesso.</p>
<h2 id="a-frente-comercial-infomach-td-synnex-e-aws">A frente comercial: Infomach, TD Synnex e AWS</h2>
<p>O delta do incidente concentrou-se em três serviços. Lambda multiplicou cerca de 100x sobre a média histórica, Step Functions cerca de 50x, e CloudWatch entre 4 e 5x. Somados, o evento representou aproximadamente 80% da conta total do mês. Em quatro madrugadas.</p>
<p>Trabalhei com a <a href="https://www.infomach.com.br/">Infomach</a>, nossa parceira AWS, articulada via TD Synnex como distribuidor. O time da Infomach atuou em duas frentes em paralelo: validação técnica do diagnóstico e desenho dos guard rails que descrevo abaixo, e abertura do ticket comercial junto à AWS apresentando o postmortem completo, a janela do incidente, a causa raiz e o plano de correção já executado. A AWS reconheceu o caso e concedeu uma fração relevante do delta como crédito. Parceria excelente em ambas as pontas, durante anos (rodamos em AWS desde 2016).</p>
<p>A lição operacional é direta: canal AWS bem ativado vira ativo estratégico. O blameless postmortem, com root cause clara e ações corretivas P0 já concluídas, foi o que sustentou a negociação. Documento técnico bem feito é insumo de negociação, não burocracia.</p>
<h2 id="os-guard-rails-que-entraram">Os guard rails que entraram</h2>
<p>Depois da contenção, o trabalho real começou. As ações P0 atacaram as três camadas que falharam.</p>
<p>No código, virou obrigatório teste de invariância para qualquer fluxo de paginação: se <code>isFinished=false</code>, então uma das três precisa ser verdadeira (cursor avançou, totalFetched aumentou, ou pageCount cresceu). Não é teste de shape, é teste de coerência entre invocações.</p>
<p>Na arquitetura, o Choice state ganhou guard rail de iterações máximas (hard stop por teto configurável por fluxo) e guard rail de progresso, em que a invocação precisa retornar evidência explícita de avanço, senão o fluxo encerra com erro de negócio. As duas proteções são complementares: o teto contém o pior caso, a invariância contém o caso comum.</p>
<p>Na observabilidade, três alarmes CloudWatch entraram com prioridade alta: duração de execução SFN acima do p99 histórico por fluxo, número de transições por execução acima de teto por fluxo, e concorrência da lambda versus baseline rolling de 7 dias. Latência de minutos, não de dias.</p>
<p>E mais duas mudanças de processo: freeze de deploy às sextas-feiras e nos últimos cinco dias do mês para pipelines billing-sensitive, e checklist obrigatório de validação D+1 após merge em SFN ou lambda de alto volume (execuções, duração, transições, custo). Janela de deploy importa: merge na quinta à noite mais execução agendada de madrugada mais final de semana na frente totalizam ~72 horas de cegueira operacional antes da primeira oportunidade de observação crítica.</p>
<p>Também construímos um robô diário de cost tracking rodando em EKS (CronJob Python via IRSA para credenciais AWS e Secrets Manager para o token do ClickUp). Ele posta o breakdown de custo por serviço e por dia no ClickUp e abre task automaticamente se houver anomalia. Sobre esse robô <a href="/blog/2026-06-03-relatorios-custo-aws-cronjob-eks/">escrevi em separado no post anterior dessa série</a>, ele virou peça central da rotina de FinOps.</p>
<h2 id="as-lições-que-ficam">As lições que ficam</h2>
<p>A lição central é simples e desconfortável: sucesso técnico pode mascarar falha de negócio. Toda invocação retornou 200, nenhum erro foi registrado, nenhum stack trace apareceu em lugar nenhum. Monitoramento baseado só em erros e exceções é insuficiente para fluxos que podem falhar por ausência de progresso. É preciso monitorar comportamento, não apenas sucesso sintático.</p>
<p>Pensa num atleta correndo numa esteira de academia: é movimento sem deslocamento. Atleta correndo na rua: movimento com deslocamento. Pela frequência cardíaca, pelo suor, pelo gasto calórico, os dois são indistinguíveis. Só o GPS responde se a pessoa saiu do lugar. Nosso fluxo tinha frequência cardíaca, tinha suor, tinha cada métrica sintática esperada no payload. O que faltava era GPS. <code>cursor=0</code>, <code>totalFetched=0</code>, <code>pageCount=1</code> era exatamente isso: indicador inequívoco de que ninguém estava saindo do lugar, ignorado porque os outros sinais estavam todos verdes. Em sistema distribuído, sinal de batimento cardíaco e sinal de deslocamento são coisas diferentes, e instrumentação madura precisa dos dois.</p>
<p>A segunda é que latência de sinal importa tanto quanto existência de sinal. Existia sinal de custo (mesmo que não consolidado 100% pela AWS), ele chegou com três dias de atraso. Em incidentes de consumo exponencial, três dias é a diferença entre um susto e um evento material. Sinais técnicos têm latência de minutos e devem ser primeira linha de defesa. Sinais financeiros são backstop, não primeira linha.</p>
<p>A terceira é mais cultural. A Step Function afetada operou de forma estável por muito tempo (desde mar/2018). Estabilidade gera familiaridade, familiaridade reduz escrutínio. Fluxos com potencial de custo exponencial merecem atenção proporcional ao seu pior caso, não à sua média histórica. Por isso entrou ação de inventário e revisão arquitetural de todos os workflows legados com potencial de loop ou custo exponencial, aplicando os mesmos guard rails onde couber.</p>
<p>A quarta, talvez a mais imediata pra quem está lendo: invariantes explícitas valem mais que happy path. Teste de shape confirma que os campos existem e têm o tipo certo. Teste de invariância confirma que os valores são coerentes entre si. Pra fluxos com loop de negócio, a invariante crítica é progresso. Se o fluxo diz pra continuar, algo precisa ter mudado. Esse tipo de invariante precisa ser explícita no código, no teste e no próprio desenho do fluxo.</p>
<p>Se você opera Step Functions ou qualquer máquina de estados com loop, dois pedidos diretos: audite hoje quais fluxos têm potencial de loop sem progresso, e veja se o sinal de defesa primário é técnico ou financeiro. Se for financeiro, você está confiando num sinal que chega com dias de atraso pra conter um evento que escala em horas.</p>
<p>Esse case está em público com um propósito específico: que outros times não descubram essa categoria de falha do mesmo jeito que a gente descobriu. Se uma única equipe ler isso, auditar seus fluxos de paginação ou loop, e encontrar uma armadilha equivalente antes dela cobrar, o post pagou o trabalho de escrever.</p>
<p>Gratidão.</p>
]]></content:encoded><category>aws</category><category>step-functions</category><category>lambda</category><category>postmortem</category><category>finops</category><category>incidente</category></item><item><title>Relatório diário de custo AWS com CronJob no EKS: encurtando a demora entre a anomalia e alguém perceber</title><link>https://rifeli.dev/blog/2026-06-03-relatorios-custo-aws-cronjob-eks/</link><pubDate>Wed, 03 Jun 2026 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/2026-06-03-relatorios-custo-aws-cronjob-eks/</guid><description>Depois de um incidente em que a conta AWS sangrou por 76 horas sem ninguém perceber, construí um CronJob no EKS que roda toda manhã, puxa o Cost Explorer, detecta anomalia com regra simples e abre task no ClickUp. Arquitetura, código e as decisões que pouparam dor.</description><content:encoded><![CDATA[<img id="image-custom" src="/images/posts/0f89c5bf-73bd-4ab6-b7f9-f960619efc84.png" alt="Gráfico de barras diárias com uma barra laranja muito acima das demais, ligada a um cartão de task aberta automaticamente" />
<p id="image-legend"></p>
<h2 id="introdução">Introdução</h2>
<p>Em março, um loop improdutivo numa Step Function rodou por 76 horas e multiplicou a conta AWS do mês da Harmo por quase 6x. Vou falar sobre isso em um artigo aqui na sexta, mas um detalhe da detecção importa pra esse post: o consumo anômalo atravessou um final de semana inteiro sem ninguém perceber, porque a única linha de defesa que pegou o problema foi a revisão manual do dashboard de billing. E revisão manual não acontece no sábado. O anomaly detectou chegou na segunda.</p>
<p>Esse post é sobre uma das contramedidas que nasceram dali: um CronJob no próprio EKS que roda toda manhã, inclusive sábado e domingo, puxa dados do Cost Explorer, compara com a média dos últimos 7 dias e posta o resultado num Doc no ClickUp. Quando detecta anomalia, abre task pra investigação. Roda há dois meses sem falhar e virou peça central da rotina de FinOps de uma operação que processa 10 milhões de pesquisas por mês. O setup todo levou uma tarde. Vou contar aqui a arquitetura, o código essencial e as decisões que pouparam dor.</p>
<h2 id="arquitetura">Arquitetura</h2>
<p>Quatro peças:</p>
<ol>
<li><strong>CronJob no EKS</strong> — roda às 8h da manhã, todos os dias.</li>
<li><strong>Script Python</strong> — chama Cost Explorer API, calcula deltas, detecta anomalia.</li>
<li><strong>IRSA</strong> — dá permissão pra o pod chamar Cost Explorer sem credencial hardcoded.</li>
<li><strong>Secrets Manager</strong> — guarda o token da API do ClickUp, lido em runtime.</li>
</ol>
<p>Output vai pra um Doc no ClickUp com histórico (append-only) e, quando detecta anomalia, cria uma task pra investigar.</p>
<p>Por que ClickUp e não Slack, e-mail ou dashboard próprio? Porque é onde o time já trabalha. Relatório que mora fora da ferramenta do dia a dia vira aba esquecida em duas semanas. Task no board que todo mundo olha de manhã tem dono, prazo e cobrança natural. O destino do alerta importa tanto quanto o alerta.</p>
<h2 id="irsa-pra-credenciais-aws">IRSA pra credenciais AWS</h2>
<p>Não precisa mais que isso. A role precisa de permissão pra Cost Explorer e pra ler do Secrets Manager:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">&#34;Version&#34;</span>: <span style="color:#f1fa8c">&#34;2012-10-17&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">&#34;Statement&#34;</span>: [
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">&#34;Effect&#34;</span>: <span style="color:#f1fa8c">&#34;Allow&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">&#34;Action&#34;</span>: [
</span></span><span style="display:flex;"><span>        <span style="color:#f1fa8c">&#34;ce:GetCostAndUsage&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#f1fa8c">&#34;ce:GetCostForecast&#34;</span>
</span></span><span style="display:flex;"><span>      ],
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">&#34;Resource&#34;</span>: <span style="color:#f1fa8c">&#34;*&#34;</span>
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">&#34;Effect&#34;</span>: <span style="color:#f1fa8c">&#34;Allow&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">&#34;Action&#34;</span>: <span style="color:#f1fa8c">&#34;secretsmanager:GetSecretValue&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">&#34;Resource&#34;</span>: <span style="color:#f1fa8c">&#34;arn:aws:secretsmanager:us-east-1:123456789012:secret:clickup/token-*&#34;</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  ]
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>A Service Account no Kubernetes anotada com a ARN da role (cobri IRSA em detalhe no <a href="/blog/2026-05-28-irsa-vs-pod-identity-eks/">post sobre IRSA vs Pod Identity</a>). Pod puxa credenciais via SDK sem código específico.</p>
<h2 id="o-script-python">O script Python</h2>
<p>Simplificado mas próximo do real:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#ff79c6">import</span> boto3
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">import</span> os
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">import</span> json
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">import</span> requests
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">from</span> datetime <span style="color:#ff79c6">import</span> date, timedelta
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>ce <span style="color:#ff79c6">=</span> boto3<span style="color:#ff79c6">.</span>client(<span style="color:#f1fa8c">&#34;ce&#34;</span>, region_name<span style="color:#ff79c6">=</span><span style="color:#f1fa8c">&#34;us-east-1&#34;</span>)
</span></span><span style="display:flex;"><span>secrets <span style="color:#ff79c6">=</span> boto3<span style="color:#ff79c6">.</span>client(<span style="color:#f1fa8c">&#34;secretsmanager&#34;</span>, region_name<span style="color:#ff79c6">=</span><span style="color:#f1fa8c">&#34;us-east-1&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">def</span> <span style="color:#50fa7b">get_clickup_token</span>():
</span></span><span style="display:flex;"><span>    resp <span style="color:#ff79c6">=</span> secrets<span style="color:#ff79c6">.</span>get_secret_value(SecretId<span style="color:#ff79c6">=</span><span style="color:#f1fa8c">&#34;clickup/token&#34;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">return</span> json<span style="color:#ff79c6">.</span>loads(resp[<span style="color:#f1fa8c">&#34;SecretString&#34;</span>])[<span style="color:#f1fa8c">&#34;token&#34;</span>]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">def</span> <span style="color:#50fa7b">get_cost_for_day</span>(day):
</span></span><span style="display:flex;"><span>    start <span style="color:#ff79c6">=</span> day<span style="color:#ff79c6">.</span>strftime(<span style="color:#f1fa8c">&#34;%Y-%m-</span><span style="color:#f1fa8c">%d</span><span style="color:#f1fa8c">&#34;</span>)
</span></span><span style="display:flex;"><span>    end <span style="color:#ff79c6">=</span> (day <span style="color:#ff79c6">+</span> timedelta(days<span style="color:#ff79c6">=</span><span style="color:#bd93f9">1</span>))<span style="color:#ff79c6">.</span>strftime(<span style="color:#f1fa8c">&#34;%Y-%m-</span><span style="color:#f1fa8c">%d</span><span style="color:#f1fa8c">&#34;</span>)
</span></span><span style="display:flex;"><span>    resp <span style="color:#ff79c6">=</span> ce<span style="color:#ff79c6">.</span>get_cost_and_usage(
</span></span><span style="display:flex;"><span>        TimePeriod<span style="color:#ff79c6">=</span>{<span style="color:#f1fa8c">&#34;Start&#34;</span>: start, <span style="color:#f1fa8c">&#34;End&#34;</span>: end},
</span></span><span style="display:flex;"><span>        Granularity<span style="color:#ff79c6">=</span><span style="color:#f1fa8c">&#34;DAILY&#34;</span>,
</span></span><span style="display:flex;"><span>        Metrics<span style="color:#ff79c6">=</span>[<span style="color:#f1fa8c">&#34;UnblendedCost&#34;</span>],
</span></span><span style="display:flex;"><span>        GroupBy<span style="color:#ff79c6">=</span>[{<span style="color:#f1fa8c">&#34;Type&#34;</span>: <span style="color:#f1fa8c">&#34;DIMENSION&#34;</span>, <span style="color:#f1fa8c">&#34;Key&#34;</span>: <span style="color:#f1fa8c">&#34;SERVICE&#34;</span>}],
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>    services <span style="color:#ff79c6">=</span> {}
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">for</span> group <span style="color:#ff79c6">in</span> resp[<span style="color:#f1fa8c">&#34;ResultsByTime&#34;</span>][<span style="color:#bd93f9">0</span>][<span style="color:#f1fa8c">&#34;Groups&#34;</span>]:
</span></span><span style="display:flex;"><span>        service <span style="color:#ff79c6">=</span> group[<span style="color:#f1fa8c">&#34;Keys&#34;</span>][<span style="color:#bd93f9">0</span>]
</span></span><span style="display:flex;"><span>        amount <span style="color:#ff79c6">=</span> <span style="color:#8be9fd;font-style:italic">float</span>(group[<span style="color:#f1fa8c">&#34;Metrics&#34;</span>][<span style="color:#f1fa8c">&#34;UnblendedCost&#34;</span>][<span style="color:#f1fa8c">&#34;Amount&#34;</span>])
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">if</span> amount <span style="color:#ff79c6">&gt;</span> <span style="color:#bd93f9">0.01</span>:
</span></span><span style="display:flex;"><span>            services[service] <span style="color:#ff79c6">=</span> amount
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">return</span> services
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">def</span> <span style="color:#50fa7b">detect_anomaly</span>(today, baseline):
</span></span><span style="display:flex;"><span>    anomalies <span style="color:#ff79c6">=</span> []
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">for</span> service, cost <span style="color:#ff79c6">in</span> today<span style="color:#ff79c6">.</span>items():
</span></span><span style="display:flex;"><span>        baseline_cost <span style="color:#ff79c6">=</span> baseline<span style="color:#ff79c6">.</span>get(service, <span style="color:#bd93f9">0</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">if</span> baseline_cost <span style="color:#ff79c6">&gt;</span> <span style="color:#bd93f9">0</span> <span style="color:#ff79c6">and</span> cost <span style="color:#ff79c6">&gt;</span> baseline_cost <span style="color:#ff79c6">*</span> <span style="color:#bd93f9">1.5</span> <span style="color:#ff79c6">and</span> cost <span style="color:#ff79c6">-</span> baseline_cost <span style="color:#ff79c6">&gt;</span> <span style="color:#bd93f9">5</span>:
</span></span><span style="display:flex;"><span>            anomalies<span style="color:#ff79c6">.</span>append({
</span></span><span style="display:flex;"><span>                <span style="color:#f1fa8c">&#34;service&#34;</span>: service,
</span></span><span style="display:flex;"><span>                <span style="color:#f1fa8c">&#34;today&#34;</span>: cost,
</span></span><span style="display:flex;"><span>                <span style="color:#f1fa8c">&#34;baseline&#34;</span>: baseline_cost,
</span></span><span style="display:flex;"><span>                <span style="color:#f1fa8c">&#34;delta_pct&#34;</span>: (cost <span style="color:#ff79c6">-</span> baseline_cost) <span style="color:#ff79c6">/</span> baseline_cost <span style="color:#ff79c6">*</span> <span style="color:#bd93f9">100</span>,
</span></span><span style="display:flex;"><span>            })
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">return</span> anomalies
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">def</span> <span style="color:#50fa7b">format_report</span>(day, total, services, anomalies):
</span></span><span style="display:flex;"><span>    lines <span style="color:#ff79c6">=</span> [<span style="color:#f1fa8c">f</span><span style="color:#f1fa8c">&#34;## </span><span style="color:#f1fa8c">{</span>day<span style="color:#ff79c6">.</span>strftime(<span style="color:#f1fa8c">&#39;</span><span style="color:#f1fa8c">%d</span><span style="color:#f1fa8c">/%m/%Y&#39;</span>)<span style="color:#f1fa8c">}</span><span style="color:#f1fa8c"> — total USD </span><span style="color:#f1fa8c">{</span>total<span style="color:#f1fa8c">:</span><span style="color:#f1fa8c">.2f</span><span style="color:#f1fa8c">}</span><span style="color:#f1fa8c">&#34;</span>, <span style="color:#f1fa8c">&#34;&#34;</span>]
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">for</span> service, cost <span style="color:#ff79c6">in</span> <span style="color:#8be9fd;font-style:italic">sorted</span>(services<span style="color:#ff79c6">.</span>items(), key<span style="color:#ff79c6">=</span><span style="color:#ff79c6">lambda</span> s: <span style="color:#ff79c6">-</span>s[<span style="color:#bd93f9">1</span>]):
</span></span><span style="display:flex;"><span>        lines<span style="color:#ff79c6">.</span>append(<span style="color:#f1fa8c">f</span><span style="color:#f1fa8c">&#34;- </span><span style="color:#f1fa8c">{</span>service<span style="color:#f1fa8c">}</span><span style="color:#f1fa8c">: USD </span><span style="color:#f1fa8c">{</span>cost<span style="color:#f1fa8c">:</span><span style="color:#f1fa8c">.2f</span><span style="color:#f1fa8c">}</span><span style="color:#f1fa8c">&#34;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">if</span> anomalies:
</span></span><span style="display:flex;"><span>        lines<span style="color:#ff79c6">.</span>append(<span style="color:#f1fa8c">&#34;&#34;</span>)
</span></span><span style="display:flex;"><span>        lines<span style="color:#ff79c6">.</span>append(<span style="color:#f1fa8c">f</span><span style="color:#f1fa8c">&#34;**</span><span style="color:#f1fa8c">{</span><span style="color:#8be9fd;font-style:italic">len</span>(anomalies)<span style="color:#f1fa8c">}</span><span style="color:#f1fa8c"> anomalia(s) detectada(s), tasks criadas.**&#34;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">return</span> {<span style="color:#f1fa8c">&#34;date&#34;</span>: day<span style="color:#ff79c6">.</span>strftime(<span style="color:#f1fa8c">&#34;%Y-%m-</span><span style="color:#f1fa8c">%d</span><span style="color:#f1fa8c">&#34;</span>), <span style="color:#f1fa8c">&#34;markdown&#34;</span>: <span style="color:#f1fa8c">&#34;</span><span style="color:#f1fa8c">\n</span><span style="color:#f1fa8c">&#34;</span><span style="color:#ff79c6">.</span>join(lines)}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">def</span> <span style="color:#50fa7b">main</span>():
</span></span><span style="display:flex;"><span>    yesterday <span style="color:#ff79c6">=</span> date<span style="color:#ff79c6">.</span>today() <span style="color:#ff79c6">-</span> timedelta(days<span style="color:#ff79c6">=</span><span style="color:#bd93f9">1</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    today_costs <span style="color:#ff79c6">=</span> get_cost_for_day(yesterday)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    baseline <span style="color:#ff79c6">=</span> {}
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">for</span> i <span style="color:#ff79c6">in</span> <span style="color:#8be9fd;font-style:italic">range</span>(<span style="color:#bd93f9">2</span>, <span style="color:#bd93f9">9</span>):
</span></span><span style="display:flex;"><span>        day <span style="color:#ff79c6">=</span> date<span style="color:#ff79c6">.</span>today() <span style="color:#ff79c6">-</span> timedelta(days<span style="color:#ff79c6">=</span>i)
</span></span><span style="display:flex;"><span>        day_costs <span style="color:#ff79c6">=</span> get_cost_for_day(day)
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">for</span> service, cost <span style="color:#ff79c6">in</span> day_costs<span style="color:#ff79c6">.</span>items():
</span></span><span style="display:flex;"><span>            baseline[service] <span style="color:#ff79c6">=</span> baseline<span style="color:#ff79c6">.</span>get(service, <span style="color:#bd93f9">0</span>) <span style="color:#ff79c6">+</span> cost
</span></span><span style="display:flex;"><span>    baseline <span style="color:#ff79c6">=</span> {k: v <span style="color:#ff79c6">/</span> <span style="color:#bd93f9">7</span> <span style="color:#ff79c6">for</span> k, v <span style="color:#ff79c6">in</span> baseline<span style="color:#ff79c6">.</span>items()}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    anomalies <span style="color:#ff79c6">=</span> detect_anomaly(today_costs, baseline)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    total <span style="color:#ff79c6">=</span> <span style="color:#8be9fd;font-style:italic">sum</span>(today_costs<span style="color:#ff79c6">.</span>values())
</span></span><span style="display:flex;"><span>    report <span style="color:#ff79c6">=</span> format_report(yesterday, total, today_costs, anomalies)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    post_to_clickup(report, anomalies, get_clickup_token())
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">if</span> <span style="color:#8be9fd;font-style:italic">__name__</span> <span style="color:#ff79c6">==</span> <span style="color:#f1fa8c">&#34;__main__&#34;</span>:
</span></span><span style="display:flex;"><span>    main()
</span></span></code></pre></div><p>Regra de anomalia é intencionalmente simples: serviço que gastou 50% acima da média dos 7 dias anteriores <strong>e</strong> a diferença absoluta é maior que USD 5. Os dois guard-rails evitam alertar sobre variação percentual em serviços que custam centavos.</p>
<h2 id="a-publicação-no-clickup">A publicação no ClickUp</h2>
<p>A API do ClickUp tem endpoints pra Docs e pra Tasks. Pro relatório diário, append num Doc fixo. Pra anomalias, cria task num list específico. Pedaço relevante:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#ff79c6">def</span> <span style="color:#50fa7b">post_to_clickup</span>(report, anomalies, token):
</span></span><span style="display:flex;"><span>    headers <span style="color:#ff79c6">=</span> {<span style="color:#f1fa8c">&#34;Authorization&#34;</span>: token, <span style="color:#f1fa8c">&#34;Content-Type&#34;</span>: <span style="color:#f1fa8c">&#34;application/json&#34;</span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    doc_id <span style="color:#ff79c6">=</span> os<span style="color:#ff79c6">.</span>environ[<span style="color:#f1fa8c">&#34;CLICKUP_DOC_ID&#34;</span>]
</span></span><span style="display:flex;"><span>    list_id <span style="color:#ff79c6">=</span> os<span style="color:#ff79c6">.</span>environ[<span style="color:#f1fa8c">&#34;CLICKUP_LIST_ID&#34;</span>]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    requests<span style="color:#ff79c6">.</span>post(
</span></span><span style="display:flex;"><span>        <span style="color:#f1fa8c">f</span><span style="color:#f1fa8c">&#34;https://api.clickup.com/api/v3/docs/</span><span style="color:#f1fa8c">{</span>doc_id<span style="color:#f1fa8c">}</span><span style="color:#f1fa8c">/pages&#34;</span>,
</span></span><span style="display:flex;"><span>        headers<span style="color:#ff79c6">=</span>headers,
</span></span><span style="display:flex;"><span>        json<span style="color:#ff79c6">=</span>{<span style="color:#f1fa8c">&#34;name&#34;</span>: report[<span style="color:#f1fa8c">&#34;date&#34;</span>], <span style="color:#f1fa8c">&#34;content&#34;</span>: report[<span style="color:#f1fa8c">&#34;markdown&#34;</span>]},
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">for</span> anomaly <span style="color:#ff79c6">in</span> anomalies:
</span></span><span style="display:flex;"><span>        requests<span style="color:#ff79c6">.</span>post(
</span></span><span style="display:flex;"><span>            <span style="color:#f1fa8c">f</span><span style="color:#f1fa8c">&#34;https://api.clickup.com/api/v2/list/</span><span style="color:#f1fa8c">{</span>list_id<span style="color:#f1fa8c">}</span><span style="color:#f1fa8c">/task&#34;</span>,
</span></span><span style="display:flex;"><span>            headers<span style="color:#ff79c6">=</span>headers,
</span></span><span style="display:flex;"><span>            json<span style="color:#ff79c6">=</span>{
</span></span><span style="display:flex;"><span>                <span style="color:#f1fa8c">&#34;name&#34;</span>: <span style="color:#f1fa8c">f</span><span style="color:#f1fa8c">&#34;Custo anormal: </span><span style="color:#f1fa8c">{</span>anomaly[<span style="color:#f1fa8c">&#39;service&#39;</span>]<span style="color:#f1fa8c">}</span><span style="color:#f1fa8c">&#34;</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#f1fa8c">&#34;description&#34;</span>: (
</span></span><span style="display:flex;"><span>                    <span style="color:#f1fa8c">f</span><span style="color:#f1fa8c">&#34;Custo de ontem: $</span><span style="color:#f1fa8c">{</span>anomaly[<span style="color:#f1fa8c">&#39;today&#39;</span>]<span style="color:#f1fa8c">:</span><span style="color:#f1fa8c">.2f</span><span style="color:#f1fa8c">}</span><span style="color:#f1fa8c">\n</span><span style="color:#f1fa8c">&#34;</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#f1fa8c">f</span><span style="color:#f1fa8c">&#34;Média 7 dias: $</span><span style="color:#f1fa8c">{</span>anomaly[<span style="color:#f1fa8c">&#39;baseline&#39;</span>]<span style="color:#f1fa8c">:</span><span style="color:#f1fa8c">.2f</span><span style="color:#f1fa8c">}</span><span style="color:#f1fa8c">\n</span><span style="color:#f1fa8c">&#34;</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#f1fa8c">f</span><span style="color:#f1fa8c">&#34;Variação: +</span><span style="color:#f1fa8c">{</span>anomaly[<span style="color:#f1fa8c">&#39;delta_pct&#39;</span>]<span style="color:#f1fa8c">:</span><span style="color:#f1fa8c">.0f</span><span style="color:#f1fa8c">}</span><span style="color:#f1fa8c">%&#34;</span>
</span></span><span style="display:flex;"><span>                ),
</span></span><span style="display:flex;"><span>                <span style="color:#f1fa8c">&#34;priority&#34;</span>: <span style="color:#bd93f9">2</span>,
</span></span><span style="display:flex;"><span>            },
</span></span><span style="display:flex;"><span>        )
</span></span></code></pre></div><p>Doc ID e List ID vêm de variáveis de ambiente, definidas no manifest do CronJob. Token via Secrets Manager.</p>
<h2 id="o-manifest-do-cronjob">O manifest do CronJob</h2>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#ff79c6">apiVersion</span>: batch/v1
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">kind</span>: CronJob
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">name</span>: aws-cost-report
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">namespace</span>: finops
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">spec</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">schedule</span>: <span style="color:#f1fa8c">&#34;0 11 * * *&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">concurrencyPolicy</span>: Forbid
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">successfulJobsHistoryLimit</span>: <span style="color:#bd93f9">3</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">failedJobsHistoryLimit</span>: <span style="color:#bd93f9">5</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">jobTemplate</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">spec</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">backoffLimit</span>: <span style="color:#bd93f9">2</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">template</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">spec</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">serviceAccountName</span>: aws-cost-report-sa
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">restartPolicy</span>: OnFailure
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">containers</span>:
</span></span><span style="display:flex;"><span>            - <span style="color:#ff79c6">name</span>: reporter
</span></span><span style="display:flex;"><span>              <span style="color:#ff79c6">image</span>: <span style="color:#bd93f9">123456789012.</span>dkr.ecr.us-east-1.amazonaws.com/aws-cost-report:latest
</span></span><span style="display:flex;"><span>              <span style="color:#ff79c6">env</span>:
</span></span><span style="display:flex;"><span>                - <span style="color:#ff79c6">name</span>: CLICKUP_DOC_ID
</span></span><span style="display:flex;"><span>                  <span style="color:#ff79c6">value</span>: <span style="color:#f1fa8c">&#34;abc123&#34;</span>
</span></span><span style="display:flex;"><span>                - <span style="color:#ff79c6">name</span>: CLICKUP_LIST_ID
</span></span><span style="display:flex;"><span>                  <span style="color:#ff79c6">value</span>: <span style="color:#f1fa8c">&#34;xyz789&#34;</span>
</span></span><span style="display:flex;"><span>              <span style="color:#ff79c6">resources</span>:
</span></span><span style="display:flex;"><span>                <span style="color:#ff79c6">requests</span>: { <span style="color:#ff79c6">cpu: 100m, memory</span>: 128Mi }
</span></span><span style="display:flex;"><span>                <span style="color:#ff79c6">limits</span>:   { <span style="color:#ff79c6">cpu: 500m, memory</span>: 256Mi }
</span></span></code></pre></div><p><code>schedule</code> em UTC. 11h UTC = 8h em Brasília, chegando antes de qualquer standup da manhã. <code>concurrencyPolicy: Forbid</code> evita rodar dois jobs sobrepostos. <code>backoffLimit: 2</code> tenta no máximo 3 vezes em caso de falha — pra um relatório diário, basta.</p>
<h2 id="detalhes-que-poupam-dor-depois">Detalhes que poupam dor depois</h2>
<ul>
<li><strong>Granularidade da Cost Explorer</strong>: ficar em <code>DAILY</code> é mais barato e mais preciso pro que a gente quer. <code>HOURLY</code> custa mais e tem delay maior.</li>
<li><strong>Cost Explorer tem delay de ~24h</strong>: dado de &ldquo;ontem&rdquo; às 8h da manhã ainda pode ser parcial em alguns serviços (Lambda, S3 Requests). Relatório é diretivo, não auditoria final. Esse delay é estrutural: sinal financeiro nunca vai ser detecção em tempo real, e é por isso que o papel desse robô é encurtar a demora de detecção de dias pra no máximo um, não zerá-la.</li>
<li><strong>Separe Doc (histórico) de Tasks (ação)</strong>: humano que vê Doc só no dia ignora; task aparece no board, é trackable.</li>
<li><strong>Tem custo a API do Cost Explorer</strong>: USD 0.01 por chamada. 8 chamadas por dia (hoje + 7 dias) dá USD 2,40/mês. Piada de preço, mas vale saber.</li>
<li><strong>Alerta pra quando o próprio job falha</strong>: se o CronJob não roda por 2 dias seguidos, você para de receber anomalias e pensa que está tudo bem. Um alert em cima do <code>failed jobs</code> do Kubernetes resolve. Detalho isso no post de observabilidade mais adiante na série.</li>
</ul>
<h2 id="lições-aprendidas">Lições aprendidas</h2>
<p>A lição central não é técnica: custo anômalo na AWS é assintomático. Não derruba serviço, não dispara exceção, não acorda ninguém de madrugada. Pressão arterial alta funciona igual. Não dói, não dá sinal, e por isso a medicina não espera sintoma: mede em toda consulta, de rotina. Quem só mede quando sente alguma coisa descobre tarde. O relatório diário é essa medição de rotina, transformando um problema invisível em número comparável todo dia, inclusive nos dias em que ninguém abriria o dashboard por conta própria.</p>
<p>A segunda lição é que detecção simples supera sofisticação ausente. A regra de 50% sobre a média de 7 dias com piso de USD 5 foi escrita numa tarde e captura a maior parte do que importa. Dá pra evoluir pra média móvel exponencial, sazonalidade, ML. Mas o robô imperfeito rodando todo dia vence o sofisticado que ficou no backlog.</p>
<p>E a terceira: sinal financeiro é backstop, não primeira linha de defesa. Ele chega com 24 a 48 horas de atraso por design. O papel dele é garantir que nenhuma anomalia atravessa um final de semana invisível, como aconteceu com a gente. A janela de minutos é trabalho de alarme técnico em cima de métrica de comportamento, e esse é assunto do post de observabilidade dessa série.</p>
<p>Se você usa o Cost Anomaly Detection nativo da AWS, fica uma pergunta honesta: ele já pegou alguma anomalia sua em tempo útil? No nosso incidente ele estava habilitado e não disparou a tempo. O porquê disso fica pro postmortem, que sai aqui na sexta.</p>
]]></content:encoded><category>aws</category><category>eks</category><category>finops</category><category>python</category><category>automação</category></item><item><title>clawtop: meu primeiro projeto open source, e por que ele não é só mais um dashboard pro Claude Code</title><link>https://rifeli.dev/blog/2026-06-01-clawtop-meu-primeiro-open-source/</link><pubDate>Mon, 01 Jun 2026 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/2026-06-01-clawtop-meu-primeiro-open-source/</guid><description>Construí um dashboard TUI multi-host pra acompanhar uso da minha subscription Claude rodando em laptop, desktop e servidor de casa ao mesmo tempo, sem expor o token OAuth. Inspirado no Clawdmeter, posicionado contra ccusage. Conta como pensei a arquitetura, os tradeoffs, e por que decidi publicar mesmo já existindo opção parecida.</description><content:encoded><![CDATA[<img id="image-custom" src="/images/posts/1219c4f3-2a22-45f8-b136-4f15c1a40288.png" alt="Interface de terminal do clawtop mostrando consumo de tokens por projeto, por host e por modelo, com nomes de projeto borrados" />
<p id="image-legend"></p>
<h2 id="a-pergunta-que-me-travou-três-dias">A pergunta que me travou três dias</h2>
<p>Construí uma ferramenta e mostrei pra um amigo. Ele olhou cinco segundos e disparou: &ldquo;era só dar <code>/usage</code> e boa, não?&rdquo;. Esse comentário me travou três dias. Não porque ele estivesse errado. Porque ele estava parcialmente certo, e eu precisava saber em que parte exatamente ele não estava, antes de seguir.</p>
<p>Esse post é sobre essa investigação, e sobre o que ela me deixou no fim: meu primeiro projeto open source de verdade, o <a href="https://github.com/leonardorifeli/clawtop">clawtop</a>. Um dashboard TUI multi-host pra acompanhar uso da subscription Claude. Sim, já existem várias ferramentas pra isso. Não, ele não é redundante com elas. Vou contar o caminho.</p>
<h2 id="como-começou">Como começou</h2>
<p>Esses dias esbarrei no <a href="https://github.com/HermannBjorgvin/Clawdmeter">Clawdmeter</a>, um projetinho muito massa ESP32 que mostra o consumo da subscription Claude num display AMOLED de duas polegadas, com sprites pixel-art do Clawd ficando mais agitados conforme você queima cota. É charmoso, é desnecessário, é o tipo de coisa que eu queria ter feito. Só que eu não tenho ESP32 sobrando, não queria pedir mais um pacote pra Aliexpress, e o que eu tenho é o cypher, meu home-server, com mais ciclos ociosos do que eu uso.</p>
<p>Mas tem um detalhe que ainda me incomodava antes mesmo de codar: eu uso Claude em três máquinas diferentes. Workstation principal, laptop de trabalho, eventualmente o próprio servidor pra testar coisas em GPU. Quando eu rodava <code>/usage</code> numa, via só o que aquela máquina sabia sobre a sessão local. O total da minha conta tava esparramado em três lugares, e nenhuma das ferramentas que olhei resolvia isso sem eu copiar o credencial OAuth pra um lugar central.</p>
<p>A primeira versão do clawtop nasceu pra resolver só o problema do display: rodar como TUI em vez de hardware. Aí veio a pergunta do amigo, eu fui investigar o que já existe, e percebi que o problema interessante não era o display. Era o que ele estava mostrando e em quantas máquinas ele estava vendo.</p>
<h2 id="o-que-já-existe-sendo-honesto">O que já existe (sendo honesto)</h2>
<p>Antes de seguir construindo, sentei e fiz a lista do que o ecossistema open source já tem em 2026.</p>
<p>O <a href="https://github.com/ryoppippi/ccusage">ccusage</a> é o padrão de facto. CLI em Node, parseia os mesmos arquivos JSONL que ficam em <code>~/.claude/projects</code> e cospe relatórios bonitos por projeto, modelo, sessão, dia. Roda on-demand. Se você quer um snapshot rápido no terminal numa máquina só, ele é melhor do que qualquer coisa que eu vá construir.</p>
<p>O <a href="https://github.com/Maciek-roboblog/Claude-Code-Usage-Monitor">Claude-Code-Usage-Monitor</a> do Maciek é o mais perto do que eu queria fazer: TUI Python rodando sempre ligada, com previsão de burn rate baseada em ML. A diferença é que ele assume que tudo roda na mesma máquina onde estão os transcripts.</p>
<p>O <a href="https://claudefa.st/blog/tools/monitors/claude-code-usage-monitor">ccflare</a> faz dashboard web com gráficos interativos, browser-based. O <a href="https://github.com/phuryn/claude-usage">claude-usage</a> é outra variação local com gráficos e estimativa de custos. E o Clawdmeter, claro, faz o display físico.</p>
<p>Lendo tudo isso, ficou claro que duas coisas ninguém estava cobrindo, ou estavam cobrindo mal: agregação multi-máquina e o modelo de segurança onde o credencial fica numa máquina diferente da que renderiza o dashboard. Esses dois pontos juntos viraram a razão de existir do clawtop. Tudo o mais é commodity.</p>
<h2 id="a-arquitetura-e-por-que-ela-tem-duas-pontas">A arquitetura, e por que ela tem duas pontas</h2>
<p>O OAuth do Claude Pro/Max vive em <code>~/.claude/.credentials.json</code> na máquina onde você roda o CLI. Esse arquivo é o portão da conta: se vaza, o atacante usa tua subscription até o token expirar. Toda ferramenta que vi assume que o consumidor desse arquivo e o renderizador do dashboard rodam no mesmo lugar. Pra quem trabalha em laptop e olha o dashboard nesse mesmo laptop, isso não importa. Pra quem quer ver em uma máquina X, importa muito: o servidor tem tunnel Cloudflare aberto, mais superfície de ataque, mais pessoas com SSH eventual. Não dá vontade de copiar o credencial pra lá.</p>
<p>Então o clawtop divide o sistema em duas peças. O daemon, chamado <code>clawtopd</code>, roda em cada máquina onde o credencial existe. Ele faz duas coisas a cada minuto: chama a API da Anthropic com Haiku e <code>max_tokens: 1</code> pra ler os headers de rate limit do response, e varre os transcripts locais em <code>~/.claude/projects/**/*.jsonl</code> agregando token por projeto, por modelo, por hora e por dia. O resultado vira um JSON pequeno que ele empurra pro servidor de visualização via SSH, escrita atômica via <code>mkdir -p &amp;&amp; cat &gt; tmp &amp;&amp; mv</code>. Sem porta exposta, sem endpoint custom, sem autenticação pra eu errar. O TUI, chamado <code>clawtop</code>, roda no servidor dentro de um tmux, lê todos os JSONs da pasta e renderiza o merge.</p>
<pre tabindex="0"><code>┌──────────────┐                          ┌──────────────────┐
│ laptop       │──clawtopd──┐             │ cypher (server)  │
│              │            │             │                  │
│ ~/.claude/   │            │             │  /var/lib/       │
│ .credentials │            │   ssh push  │   clawtop/       │
└──────────────┘            ├────atomic──▶│   laptop.json    │
                            │             │   omen.json      │
┌──────────────┐            │             │   workpc.json    │
│ omen         │──clawtopd──┤             │        │         │
└──────────────┘            │             │        ▼         │
                            │             │  clawtop (TUI)   │
┌──────────────┐            │             │  inside tmux     │
│ workpc       │──clawtopd──┘             └──────────────────┘
└──────────────┘
        │
        │ HTTPS (1 Haiku token / min, fração de centavo / dia)
        ▼
   api.anthropic.com
</code></pre><p>O credencial nunca sai da workstation. Se o cypher for invadido amanhã, o atacante leva JSONs com percentuais e contadores. Não leva o token. Muito menos vai conseguir acessar as demais máquinas na rede.</p>
<h2 id="a-parte-multi-host-que-é-o-truque-inteiro">A parte multi-host, que é o truque inteiro</h2>
<p>A sacada que tornou o projeto não-redundante foi perceber que o rate limit da Anthropic é por conta, não por máquina. Os três daemons rodando ao mesmo tempo veem todos o mesmo percentual de utilização nas janelas de cinco horas e sete dias. Mas o breakdown por projeto e por modelo vem dos transcripts locais e é por máquina. Cada um sabe só do que rodou ali.</p>
<p>A combinação é que dá o ganho. O servidor de visualização recebe três JSONs diferentes, um por máquina. Na hora de renderizar, o TUI faz merge: pega o rate limit mais fresco, soma os tokens por projeto e por modelo, soma os buckets horários e diários elemento a elemento, e o detalhe que mais me deixou orgulhoso: preserva atribuição por host nos projetos que aparecem em mais de uma máquina. Se eu rodo <code>rifeli.dev</code> tanto no omen (PC1) quanto no notebook, a linha aparece como <code>rifeli.dev 874k</code> no total, com uma sub-linha discreta embaixo dizendo <code>omen 800k · notebook 74k</code>. Em projetos single-host, a sub-linha mostra a contagem de sessões distintas no período.</p>
<p>Pra quem roda Claude numa máquina só, o merge de um elemento é identidade e tudo funciona igual. Pra quem roda em três, é a diferença entre ter três relatórios separados e ter uma resposta clara pra &ldquo;onde foi minha cota essa semana, e em qual máquina&rdquo;.</p>
<img id="image-custom" src="/images/posts/2514427f-0305-4293-8bb5-9abd72ff33da.jpeg" alt="Monitor sobre bancada de madeira exibindo o clawtop em tela cheia no home server" />
<p id="image-legend">Clawtop rodando no meu home-server</p>
<h2 id="o-que-o-dashboard-mostra-hoje">O que o dashboard mostra hoje</h2>
<p>Depois de várias iterações respondendo perguntas reais de uso, o TUI tem seis abas ou, quando o terminal é grande o suficiente, um modo dashboard denso que mostra tudo numa tela só. As perguntas que ele responde:</p>
<p><strong>Limits</strong>: percentual usado das janelas de cinco horas e sete dias, com countdown pro reset. A partir do v0.8 ele também calcula a taxa de queima a partir do histórico das últimas dezenas de polls e projeta: <code>+8.2%/h · 100% em 4h 12m · OVER!</code> quando o ritmo atual indica que você vai bater o limite antes da janela resetar. Verde &ldquo;ok&rdquo;, amarelo &ldquo;close&rdquo;, vermelho &ldquo;OVER!&rdquo; quando dá merda.</p>
<p><strong>Projects</strong>: os projetos que mais consumiram tokens no período, com barra relativa, total formatado, contagem de sessões e &ldquo;última vez tocado&rdquo; pra distinguir o que tá ativo do que é só lixo histórico dentro da janela. Quando duas ou mais máquinas contribuíram pro mesmo projeto, mostra a atribuição por host.</p>
<p><strong>Models</strong>: split por modelo (Opus, Sonnet, Haiku) com input, output, cache read, cache create, <strong>taxa de cache hit em porcentagem mais o equivalente em tokens economizados</strong>, e a contagem de sessões em que cada modelo foi usado. A linha do cache hit foi a que mais me surpreendeu: meus 99% de cache hit equivalem a centenas de milhões de tokens que eu não precisei mandar de novo, e nenhuma ferramenta que eu vi torna isso visível.</p>
<p><strong>Hosts</strong>: tabela com cada máquina mostrando tokens contribuídos, projetos distintos, sessions distintas, freshness do último push. Quem é o cavalo, quem é o pônei, quem morreu.</p>
<p><strong>Sessions</strong>: top 10 conversas mais caras do período, com projeto, modelo, duração e quando foi a última atividade. Detecta runaway antes de virar problema.</p>
<p><strong>Hourly e Daily</strong>: sparkline de 24 horas em granularidade horária, e sparkline de 7 dias em granularidade diária. Identifica padrão de uso ao longo do dia e ao longo da semana.</p>
<p>A tecla <code>t</code> alterna entre o modo tabbed e o modo dashboard denso. A tecla <code>f</code> cicla um filtro por host (all → omen → notebook → all), pra quando você quer focar só numa máquina. Cores das barras: vermelho/laranja/verde nas barras de limit (alto é ruim), cyan único nas barras de projeto e modelo (rank não tem semântica de bom/ruim).</p>
<h2 id="as-decisões-que-valeram-a-pena">As decisões que valeram a pena</h2>
<p>A primeira foi <strong>Go nos dois lados</strong>. Pensei em Python pro daemon, é mais rápido de rabiscar. Decidi Go porque o resultado é um binário único cross-compilável que copia com <code>scp</code> e roda. Sem virtualenv no servidor, sem <code>pip install</code>, sem versão de Python pra controlar. Pra um projeto que eu quero deixar funcionando por anos, vale o boilerplate extra.</p>
<p>A segunda foi <strong>bubbletea pra TUI</strong>. A primeira versão eu fiz com <code>fmt.Print</code> e ANSI escapes na mão. Funcionou em dez minutos e era impossível de evoluir. Troquei pelo bubbletea com lipgloss e o código ficou mais curto, não maior. Cada nova tela que apareceu na evolução foi quase de graça depois que o framework estava de pé.</p>
<p>A terceira foi <strong>reler o credencial a cada poll</strong>. A primeira tentativa carregava o credencial no startup e implementava refresh do OAuth no próprio daemon. Implementação chata, fácil de errar, e me deixaria com duas cópias do refresh logic, a do Claude CLI e a minha. Joguei tudo fora. Hoje o daemon reabre <code>~/.claude/.credentials.json</code> a cada chamada. Se o CLI refrescou, eu pego o novo. Se não, falho explicitamente e o systemd reinicia. Menos código, menos jeitos de quebrar.</p>
<p>A quarta foi sobre <strong>descobrir o nome real do projeto a partir dos transcripts</strong>. Os diretórios em <code>~/.claude/projects</code> têm nomes tipo <code>-home-rifeli-projects-personal-rifeli-dev</code>, onde <code>-</code> codifica tanto <code>/</code> quanto <code>.</code>. Ou seja, dá pra ser tanto <code>/home/rifeli/projects/personal/rifeli/dev</code> quanto <code>/home/rifeli/projects/personal/rifeli.dev</code>. Tentei várias estratégias de heurística pra decodificar e todas erravam em alguns casos. Quase desisti. Aí olhei dentro do JSONL e vi que cada mensagem do tipo <code>user</code> tem um campo <code>cwd</code> com o path absoluto, exato. Joguei o decoder fora, passei a usar <code>cwd</code> como verdade. Lição: antes de inventar parser, leia o arquivo até o fim \o/.</p>
<p>A quinta foi <strong>preservar último valor bom em caso de falha do probe</strong>. Na primeira release multi-host, o daemon zerava os percentuais quando a Anthropic devolvia 429 ou caía momentaneamente. O dashboard piscava em zero, depois voltava no próximo poll. Bug ridículo. Hoje o daemon guarda o último probe bem-sucedido em memória; se o próximo falhar, ele reusa os valores anteriores no JSON que escreve. Resultado: dashboard não pisca, e quando você olha o log vê que houve falha mas a tela continua útil.</p>
<p>A sexta foi o <strong>modelo de release com GoReleaser + install.sh idempotente</strong>. Cada tag dispara cross-compile pra Linux/macOS × amd64/arm64, gera <code>.deb</code> e <code>.rpm</code>, sobe pra GitHub Releases. O <code>install.sh</code> baixa o binário certo, deixa as units do systemd no lugar e, a partir do v0.8, detecta quais services tão habilitados e reinicia eles sozinho. Atualizar virou um comando: <code>sh install.sh</code>. Esse foi puxado por um pedido direto de teste real, que é a próxima seção.</p>
<h2 id="testando-como-um-usuário-de-verdade">Testando como um usuário de verdade</h2>
<p>Cortei a primeira release achando que tava pronto. Aí fui ser o primeiro usuário do meu próprio projeto, seguindo o README, e cada passo revelou alguma fricção:</p>
<p>O <code>install.sh</code> aceitava silenciosamente flag desconhecida — <code>install.sh daemon --host=cypher</code> comia o <code>--host=cypher</code> sem reclamar e o usuário pensava que tinha configurado. Corrigi pro script rejeitar argumento desconhecido com erro claro.</p>
<p>O alias SSH do meu cypher estava quebrado naquele dia (DNS bagunçado), e o <code>clawtopd doctor</code> mostrou exatamente isso: <code>FAIL ssh cypher: Permission denied (publickey)</code>, com <code>HINT: ssh-copy-id cypher</code>. Eu não tinha feito ssh-copy-id depois do reinstall do servidor. Sem o doctor, eu ia perder uma hora investigando. Doctor é a coisa mais barata de implementar que mais valor entrega.</p>
<p>O terminal que eu uso quebrava URLs longas no paste, transformando <code>curl ... | sh</code> em dois comandos separados que falhavam de jeitos confusos. Documentei o workaround com <code>git clone /tmp/c &amp;&amp; sh /tmp/c/install.sh</code> no INSTALL.md.</p>
<p>A unit do systemd do viewer tinha uma flag desatualizada (<code>--path</code> em vez de <code>--dir</code>) que eu esqueci de atualizar quando renomeei. O serviço entrava em loop de start/fail/restart silenciosamente. Foi tipo o terceiro patch release, e nada vence cargo cult: você precisa de um usuário real fazendo o caminho real pra esses bugs aparecerem.</p>
<p>Cada uma dessas fricções virou commit, virou release, virou nota de troubleshooting no INSTALL.md. O projeto que existe hoje tem uma forma diferente do que eu publiquei na primeira tag, e cada diferença foi puxada por um momento de &ldquo;ah, isso aqui tá chato&rdquo;.</p>
<h2 id="o-que-ficou-de-fora-e-por-que">O que ficou de fora, e por que</h2>
<p>Não tem TLS entre o daemon e o servidor porque SSH já é o TLS dessa conexão. Não tem queue, não tem retry com backoff agressivo, não tem persistência local de leituras. Se uma chamada falha, eu logo, espero um minuto, tento de novo. O dado é descartável, a próxima leitura sobrescreve. Quanto menos estado, menos coisa quebra às três da manhã.</p>
<p>Não tem auth via API key normal porque API key normal não devolve os headers da subscription, devolve os da tier de API. Pra ler <code>anthropic-ratelimit-unified-5h-utilization</code> da minha conta Team, OAuth é obrigatório.</p>
<p>Não tem dashboard web. Pensei em hospedar uma página HTML no cypher, mas era exatamente a coisa que não dava pra fazer sem expor algo novo no tunnel Cloudflare. A TUI dentro do tmux resolve sem novos vetores de ataque.</p>
<p>Não tem ML burn-rate prediction. O <a href="https://github.com/Maciek-roboblog/Claude-Code-Usage-Monitor">monitor do Maciek</a> já faz isso muito bem com ML. Meu burn rate é regressão linear simples sobre os últimos polls — funciona o suficiente pra avisar &ldquo;vai bater limite antes de resetar&rdquo; sem complicar o código. Se você quer projeção sofisticada, use a ferramenta dele junto.</p>
<p>Não tem suporte a Windows. Linux e macOS por enquanto. Adicionar Windows não é difícil tecnicamente (é só compilar) mas demanda testar o caminho do credencial e do tmux equivalente, e eu não uso Windows. Issue aberta pra quem quiser contribuir.</p>
<h2 id="o-json-que-voa">O JSON que voa</h2>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">&#34;schema&#34;</span>: <span style="color:#bd93f9">3</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">&#34;machine&#34;</span>: <span style="color:#f1fa8c">&#34;omen&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">&#34;ts&#34;</span>: <span style="color:#bd93f9">1716688320</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">&#34;session&#34;</span>: { <span style="color:#ff79c6">&#34;pct&#34;</span>: <span style="color:#bd93f9">31.0</span>, <span style="color:#ff79c6">&#34;reset_at&#34;</span>: <span style="color:#bd93f9">1716700000</span> },
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">&#34;week&#34;</span>:    { <span style="color:#ff79c6">&#34;pct&#34;</span>:  <span style="color:#bd93f9">5.0</span>, <span style="color:#ff79c6">&#34;reset_at&#34;</span>: <span style="color:#bd93f9">1717100000</span> },
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">&#34;limit&#34;</span>:   <span style="color:#f1fa8c">&#34;allowed&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">&#34;subscription&#34;</span>: <span style="color:#f1fa8c">&#34;team&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">&#34;window&#34;</span>:  <span style="color:#f1fa8c">&#34;7d&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">&#34;sessions&#34;</span>: <span style="color:#bd93f9">20</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">&#34;by_project&#34;</span>: [
</span></span><span style="display:flex;"><span>    { <span style="color:#ff79c6">&#34;name&#34;</span>: <span style="color:#f1fa8c">&#34;rifeli&#34;</span>, <span style="color:#ff79c6">&#34;path&#34;</span>: <span style="color:#f1fa8c">&#34;/home/rifeli/projects/personal/rifeli&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">&#34;in&#34;</span>: <span style="color:#bd93f9">12345</span>, <span style="color:#ff79c6">&#34;out&#34;</span>: <span style="color:#bd93f9">1900000</span>, <span style="color:#ff79c6">&#34;cache_read&#34;</span>: <span style="color:#bd93f9">45000000</span>, <span style="color:#ff79c6">&#34;cache_create&#34;</span>: <span style="color:#bd93f9">1400000</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">&#34;sessions&#34;</span>: <span style="color:#bd93f9">5</span>, <span style="color:#ff79c6">&#34;last_at&#34;</span>: <span style="color:#bd93f9">1716685200</span> }
</span></span><span style="display:flex;"><span>  ],
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">&#34;by_model&#34;</span>: [
</span></span><span style="display:flex;"><span>    { <span style="color:#ff79c6">&#34;model&#34;</span>: <span style="color:#f1fa8c">&#34;claude-opus-4-7&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">&#34;in&#34;</span>: <span style="color:#bd93f9">200800</span>, <span style="color:#ff79c6">&#34;out&#34;</span>: <span style="color:#bd93f9">4300000</span>, <span style="color:#ff79c6">&#34;cache_read&#34;</span>: <span style="color:#bd93f9">462500000</span>, <span style="color:#ff79c6">&#34;cache_create&#34;</span>: <span style="color:#bd93f9">14600000</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">&#34;sessions&#34;</span>: <span style="color:#bd93f9">32</span> }
</span></span><span style="display:flex;"><span>  ],
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">&#34;hourly_24h&#34;</span>: [<span style="color:#bd93f9">0</span>, <span style="color:#bd93f9">0</span>, <span style="color:#bd93f9">0</span>, <span style="color:#bd93f9">0</span>, <span style="color:#bd93f9">0</span>, <span style="color:#bd93f9">0</span>, <span style="color:#bd93f9">0</span>, <span style="color:#bd93f9">0</span>, <span style="color:#bd93f9">0</span>, <span style="color:#bd93f9">0</span>, <span style="color:#bd93f9">0</span>, <span style="color:#bd93f9">0</span>, <span style="color:#bd93f9">0</span>, <span style="color:#bd93f9">0</span>, <span style="color:#bd93f9">0</span>, <span style="color:#bd93f9">107835</span>, <span style="color:#bd93f9">7436</span>, <span style="color:#bd93f9">0</span>, <span style="color:#bd93f9">0</span>, <span style="color:#bd93f9">0</span>, <span style="color:#bd93f9">0</span>, <span style="color:#bd93f9">0</span>, <span style="color:#bd93f9">0</span>, <span style="color:#bd93f9">178055</span>],
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">&#34;daily_7d&#34;</span>:   [<span style="color:#bd93f9">120000</span>, <span style="color:#bd93f9">250000</span>, <span style="color:#bd93f9">80000</span>, <span style="color:#bd93f9">400000</span>, <span style="color:#bd93f9">600000</span>, <span style="color:#bd93f9">1100000</span>, <span style="color:#bd93f9">1300000</span>],
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">&#34;top_sessions&#34;</span>: [
</span></span><span style="display:flex;"><span>    { <span style="color:#ff79c6">&#34;id&#34;</span>: <span style="color:#f1fa8c">&#34;abc-...&#34;</span>, <span style="color:#ff79c6">&#34;project&#34;</span>: <span style="color:#f1fa8c">&#34;rifeli&#34;</span>, <span style="color:#ff79c6">&#34;model&#34;</span>: <span style="color:#f1fa8c">&#34;claude-opus-4-7&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">&#34;in&#34;</span>: <span style="color:#bd93f9">186931</span>, <span style="color:#ff79c6">&#34;out&#34;</span>: <span style="color:#bd93f9">966588</span>, <span style="color:#ff79c6">&#34;started_at&#34;</span>: <span style="color:#bd93f9">1716680000</span>, <span style="color:#ff79c6">&#34;last_at&#34;</span>: <span style="color:#bd93f9">1716688000</span> }
</span></span><span style="display:flex;"><span>  ]
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Um a cinco kilobytes por máquina, schema versionado, sem nada sensível. Viewers antigos ignoram campos desconhecidos, viewers novos default-zero os que não vieram. Forward-compat barato.</p>
<h2 id="por-que-publicar-mesmo-já-tendo-alternativa">Por que publicar mesmo já tendo alternativa</h2>
<p>Esse é meu primeiro projeto open source de verdade, e a pergunta &ldquo;isso já existe&rdquo; me travou três dias antes de eu entender que ela está mal formulada. A pergunta certa é: &ldquo;isso resolve um problema específico que as alternativas não resolvem para um conjunto de pessoas&rdquo;. Se a resposta for sim, mesmo que o conjunto seja pequeno, vale publicar. Se a resposta for não, faça issue no projeto existente pedindo a feature.</p>
<p>No meu caso: tem gente que roda Claude em mais de uma máquina, e tem gente que se incomoda em copiar credencial pra servidor. Esses dois conjuntos somados não são o mundo, mas existem. E a forma de descobrir se existem mesmo é publicar e ver se chega issue ou PR. Eu vou descobrir junto com vocês.</p>
<h2 id="como-rodar">Como rodar</h2>
<p>Quando estiver na máquina que tem Claude rodando:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>curl -fsSL https://raw.githubusercontent.com/leonardorifeli/clawtop/main/install.sh | sh
</span></span><span style="display:flex;"><span>clawtopd doctor <span style="color:#6272a4"># confere creds, anthropic, destino</span>
</span></span><span style="display:flex;"><span>systemctl --user <span style="color:#8be9fd;font-style:italic">enable</span> --now clawtopd-local
</span></span><span style="display:flex;"><span>clawtop --dir<span style="color:#ff79c6">=</span>~/.local/share/clawtop
</span></span></code></pre></div><p>Se o terminal quebrar a URL no paste (alguns terminais fazem), troca por <code>git clone https://github.com/leonardorifeli/clawtop /tmp/c &amp;&amp; sh /tmp/c/install.sh</code>.</p>
<p>Pra setup multi-host com viewer separado, o passo a passo completo está em <a href="https://github.com/leonardorifeli/clawtop/blob/main/deploy/INSTALL.md"><code>deploy/INSTALL.md</code></a>. Cerca de mil linhas de Go no total, MIT, PR aberto a quem quiser melhorar.</p>
<h2 id="créditos">Créditos</h2>
<p>Sem o <a href="https://github.com/HermannBjorgvin">Hermann Bjorgvin</a> e o <a href="https://github.com/HermannBjorgvin/Clawdmeter">Clawdmeter</a> eu não saberia que a Anthropic expõe esses headers via OAuth. Sem o <a href="https://github.com/ryoppippi">ryoppippi</a> e o <a href="https://github.com/ryoppippi/ccusage">ccusage</a> eu provavelmente teria parado na pergunta do amigo. Construir em cima de prior art é fazer parte da conversa, não substituir.</p>
]]></content:encoded><category>go</category><category>selfhost</category><category>claude</category><category>open-source</category><category>ferramentas</category></item><item><title>4x mais entrega no mesmo mês: 30 dias dentro do Claude Code</title><link>https://rifeli.dev/blog/2026-05-29-30-dias-claude-code-usd-8k-plano-fixo/</link><pubDate>Fri, 29 May 2026 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/2026-05-29-30-dias-claude-code-usd-8k-plano-fixo/</guid><description>Em 30 dias dentro do Claude Code entreguei o equivalente a quatro meses de roadmap. O custo equivalente em tokens (USD 8.069 num plano fixo de assinatura) é só dimensão. O que importa é o que saiu do outro lado.</description><content:encoded><![CDATA[<img id="image-custom" src="/images/posts/e0f403dc-d31c-496e-a3f8-38f912b334d6.png" alt="Painel de terminal com as estatísticas de 30 dias de uso: 68 sessões, 1.024 turnos, 12.824 respostas, 96,6% de cache hit e desempenho de 4x" />
<p id="image-legend">O que importa não é USD 8.069 em tokens. É comprimir quatro meses de roadmap em um.</p>
<h2 id="introdução">Introdução</h2>
<p>Em 30 dias dentro do Claude Code, entreguei o equivalente a <strong>quatro meses de roadmap</strong>.</p>
<p>Não é métrica de IDE. É auto-observação calibrada por anos no mesmo conjunto de projetos. O que normalmente levaria 8 meses, coube em 2.</p>
<p>A maior parte desse roadmap aconteceu dentro da Harmo, infraestrutura Drive-to-Store que opera +60 mil lojas, processa +10 milhões de pesquisas e +300 mil avaliações por mês. Esse é o terreno em que esses 30 dias foram medidos.</p>
<p>Pra calibrar o tamanho do uso: rodei o equivalente a USD 8.069 em tokens nesses 30 dias, pagando um plano fixo de assinatura. Esse número não é a tese. É dimensão, ajuda a entender quanto a ferramenta esteve no fluxo. O que conta é o que saiu do outro lado.</p>
<h2 id="como-cheguei-nesses-números">Como cheguei nesses números</h2>
<p>O Claude Code persiste cada sessão em <code>~/.claude/projects/&lt;encoded-cwd&gt;/&lt;session-uuid&gt;.jsonl</code>. Cada linha é um evento estruturado: mensagem do humano, resposta do assistant, tool call, tool result. No caso do assistant, vem com contagem de tokens por categoria (input, output, cache write, cache read) e identificação do modelo usado.</p>
<p>Escrevi um script Python pequeno que soma sessões, turnos humanos, respostas, tokens por categoria e estima custo equivalente em USD usando a tabela pública da Anthropic. Roda local, sem mandar nada pra fora, com sanitização automática de credenciais antes de gravar relatório. Detalhes no apêndice.</p>
<p>Rodei nas duas máquinas que uso ativamente (desktop e notebook) e somei. Isso só funciona porque mantenho Claude Code sincronizado entre elas (com a pegadinha das memórias <code>path-encoded</code> resolvida), setup que detalhei em <a href="https://rifeli.dev/blog/sincronizando-claude-code-entre-maquinas/">Sincronizando Claude Code entre máquinas</a>.</p>
<h2 id="o-que-saiu-desses-30-dias">O que saiu desses 30 dias</h2>
<p>Em ordem de impacto, e citando só o que pode ir publicamente:</p>
<ul>
<li>Diagnóstico e ações ao vivo de uma crise de performance no Aurora PostgreSQL da Harmo (CPU 90% sustentada, 18 índices mortos, write amplification gratuita), com runbook documentado para o próximo incidente.</li>
<li>Refator de consumer single-row para batch insert com <code>ON CONFLICT</code> num pipeline crítico da Harmo, levando o gargalo de 47 segundos para 1,4 segundo em 10 mil eventos. Eliminou uma classe inteira de incidentes futuros.</li>
<li>Avanços no produto principal da Harmo equivalentes a meses do roadmap normal. Não dá para detalhar publicamente, mas é a maior parte do ×4 que abriu esse post. Inclui evolução da FloraAI (plataforma de chat conversacional com LLM, MCP, tools, etc), a camada de inteligência transversal que atravessa todos os planos da plataforma.</li>
<li>Várias contribuições com comunidade tech.</li>
<li>Mais de 10 reports internos + Gestão estratégica do nosso cluster EKS.</li>
<li>Reforma da infraestrutura de SEO, analytics e privacidade do próprio blog rifeli.dev: canonical e Open Graph dinâmicos, JSON-LD Article, banner LGPD, política de privacidade. Coisa que estava em backlog há meses, fechada numa sessão.</li>
<li>Um slash command custom no Claude Code (<code>/save-session</code>) e o script de análise (<code>claude-code-stats.py</code>) que gerou os próprios números desse post (vai pra outro artigo falando sobre ele).</li>
<li>O <a href="https://github.com/leonardorifeli/clawtop">clawtop</a>, meu primeiro open source de verdade: dashboard TUI multi-host pra acompanhar uso da subscription Claude, com daemon em cada máquina e renderização centralizada num servidor de casa. Vai virar post próprio mais adiante na série \o/.</li>
</ul>
<p>Nem tudo isso &ldquo;veio do Claude Code&rdquo;. Mas todos vieram mais rápido porque ele estava aberto no segundo monitor o tempo todo. No final, cada commit precisa carregar uma assinatura né.</p>
<h4 id="sobre-nosso-eks">Sobre nosso EKS</h4>
<p>A espinha dorsal da Plataforma Harmo é um cluster Kubernetes gerenciado na AWS (EKS). São cerca de 90 microsserviços rodando em torno de 250 pods, a maior parte sobre instâncias ARM/Graviton, escolha que nos dá melhor relação custo por desempenho. Em vez de manter um parque fixo de servidores, usamos <a href="https://karpenter.sh/">Karpenter</a> para provisionar e consolidar nós automaticamente, de modo que a capacidade acompanha a carga minuto a minuto. Isso sustenta o volume de processamento da operação, na casa de entenas de milhares de avaliações e milhões de pesquisas por mês, mantendo a conta de infraestrutura sob controle.</p>
<h4 id="contribuições-no-período">Contribuições no período:</h4>
<ul>
<li>1407 commits.</li>
<li>189 PRs envolvidos no período.</li>
<li>50 code reviews em 40 dias, espalhados em 16 repos. 64 PRs onde participei comentando.</li>
<li>46+ repos diferentes tocados.</li>
</ul>
<h2 id="anatomia-do-uso">Anatomia do uso</h2>
<p>68 sessões em 30 dias. 1.024 turnos humanos. 12.824 respostas do assistant. Em média, 12 respostas para cada coisa que eu digito. Bash domina como ferramenta usada (3.956 chamadas combinadas), seguido de Read (1.072) e Edit (969). Cache hit ratio de 96,6%.</p>
<p>Mediana de turnos humanos por sessão é baixa: 4 a 6. 52% das sessões são one-shot (≤ 4 turnos), 20% são ping-pong (&gt; 10 turnos). A maioria das interações é cirúrgica, não conversa. Encomendo coisas. Quando preciso conversar, é em sessões de debug profundo, e essas pagam a fatura sozinhas.</p>
<p>A sessão mais cara do mês teve 152 turnos, 59 horas de relógio e custo equivalente de USD 1.741. Foi diagnóstico de incidente que destravou uma feature da Harmo em rota direta com o cliente. Em momentos assim, dimensão de uso vira diferencial de produto.</p>
<h2 id="por-que-isso-comprime-tempo">Por que isso comprime tempo</h2>
<p>O número que mais explica o ×4 não é leverage de preço. É a razão 1.024 turnos meus contra 12.824 respostas do assistant. Cada coisa que digito vira, em média, 12 etapas executadas para mim: leitura de arquivo, escrita, busca, decisão, execução de bash, novo prompt para si mesmo. Bash domina como ferramenta porque a maior parte do trabalho técnico real é orquestração de comandos, não geração de prosa.</p>
<p>Não é &ldquo;ChatGPT na linha de comando&rdquo;. É um orquestrador que recebe intenção e devolve cadeia de ações. Compressão de tempo vem dessa relação de leverage por turno, repetida ao longo de horas, repetida ao longo de semanas. No fim do mês, sobra uma diferença de patamar entre o que eu teria feito sozinho e o que entreguei.</p>
<h2 id="toquei-em-13-projetos-diferentes">Toquei em 13 projetos diferentes</h2>
<p>Oito com sessões reais. Os três que dominaram: a plataforma core da Harmo (mais de 70 microservices, +10 milhões de pesquisas e +300 mil avaliações por mês, em mais de 60 mil lojas), um motor de NLP da camada FloraAI da Harmo (que transforma voz de cliente em decisão operacional), e um repositório pessoal (esse blog). Contextos absurdamente diferentes em stack, domínio, vocabulário e decisões de arquitetura.</p>
<p>Variedade de stack e domínio que normalmente quebraria a coerência de qualquer assistente; o Claude Code carrega isso sem que eu reapresente o projeto a cada nova sessão. Pico de horário fica entre 12h-15h e 18h-21h (de manhã, geralmente em reuniões e análises estratégicas), com cauda longa até depois das 23h. Quarta-feira é o dia mais ativo. Usei a ferramenta em cerca de 70% dos dias do mês.</p>
<h2 id="o-que-mudou-no-meu-fluxo">O que mudou no meu fluxo</h2>
<p>Três coisas, sem ordem de importância.</p>
<p>A primeira foi começar a usar a ferramenta sem culpa de &ldquo;isso vale o token?&rdquo;. Plano fixo, custo marginal por uso adicional é zero, mas o instinto de &ldquo;paga por chamada&rdquo; de eras anteriores levou uns dias para sair.</p>
<p>A segunda foi virar default ter Claude Code aberto no segundo monitor. Não como assistente eventual, mas como ambiente de trabalho. A fronteira de &ldquo;isso eu faço, isso eu delego&rdquo; se move sem decisão consciente. Você se pega delegando algo que no mês passado teria escrito à mão.</p>
<p>A terceira foi escrever ferramentaria em cima da ferramenta. Construí um slash command (<code>/save-session</code>) que destila o que vale carregar para a próxima conversa antes de fechar a sessão. Sem isso, contexto entre conversas virava arquivo morto. Esse vira tema de um post próprio mais adiante na série.</p>
<h2 id="fechamento">Fechamento</h2>
<p>O número que importa não é USD 8.069 em tokens. Nem 80x de leverage sobre o que pagaria via API. É que comprimi cerca de quatro meses de roadmap em um.</p>
<p>A pergunta certa para quem avalia Claude Code não é &ldquo;quanto custa por hora de uso&rdquo;, nem &ldquo;quanto economiza em tokens&rdquo;. É &ldquo;quanto avança o produto enquanto a equipe inteira vai dormir&rdquo;.</p>
<h2 id="apêndice-o-script">Apêndice: o script</h2>
<p>O script Python que usei está em <code>.claude/scripts/claude-code-stats.py</code>. Lê <code>~/.claude/projects/</code> localmente, soma os últimos 30 dias (configurável), sanitiza credenciais detectadas em prompts e exporta markdown + JSON. Roda em Linux e macOS sem dependência externa além de Python 3.9+. Útil para rodar antes de qualquer conversa sobre &ldquo;como você usa Claude Code&rdquo; com o time. Os números reais costumam surpreender quem responde de cabeça.</p>
]]></content:encoded><category>claude-code</category><category>ai</category><category>dev-tools</category><category>produtividade</category><category>anthropic</category></item><item><title>IRSA vs Pod Identity no EKS: diferenças, armadilhas e um bug silencioso que me custou duas horas</title><link>https://rifeli.dev/blog/2026-05-28-irsa-vs-pod-identity-eks/</link><pubDate>Thu, 28 May 2026 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/2026-05-28-irsa-vs-pod-identity-eks/</guid><description>Como IRSA funciona no EKS, o que mudou com Pod Identity, quando preferir um sobre o outro, e um bug real: AccessDeniedException causado por webhook de Pod Identity não instalado no cluster.</description><content:encoded><![CDATA[<img id="image-custom" src="/images/posts/5e48087d-d8f7-4bb8-bef5-e013c9f238a3.png" alt="Comparação em dois painéis: à esquerda o caminho longo e sinuoso do IRSA passando por várias peças até o pod, à direita a ligação direta do Pod Identity" />
<p id="image-legend">IRSA vs Pod Identity</p>
<h2 id="introdução">Introdução</h2>
<p>Na Harmo, +70 microservices (Golang, python e node.js) rodam em EKS atendendo +10 milhões de pesquisas e +300 mil avaliações públicas por mês. Cada um desses serviços precisa de credencial AWS pra acessar S3, Secrets Manager, SQS, DynamoDB, o que for. Por anos, IRSA foi a resposta: IAM Role atrelada a Service Account via OIDC, e fim. Em 2023 a AWS lançou Pod Identity, que faz a mesma coisa de forma mais simples. Hoje os dois coexistem, e a dúvida de qual usar aparece em cluster novo e em qualquer conversa de migração.</p>
<p>Esse post é o destilado do que aprendi configurando, quebrando e migrando entre os dois nos últimos meses, incluindo um bug silencioso que me custou umas duas horas num cluster antigo. A mensagem de erro da AWS não ajudou em absolutamente nada.</p>
<h2 id="como-irsa-funciona">Como IRSA funciona</h2>
<p>IRSA associa uma Service Account do Kubernetes a uma IAM Role usando o provedor OIDC do cluster EKS. Em uma frase: você registra o OIDC issuer do cluster como Identity Provider na conta AWS, cria uma IAM Role cuja trust policy aceita <code>sts:AssumeRoleWithWebIdentity</code> vindo desse issuer pra uma Service Account específica, e anota a SA no Kubernetes com <code>eks.amazonaws.com/role-arn</code>. O EKS injeta token projetado e variáveis de ambiente no pod, e o SDK AWS faz o <code>AssumeRoleWithWebIdentity</code> sozinho.</p>
<p>A trust policy da role é onde mora a complexidade:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">&#34;Version&#34;</span>: <span style="color:#f1fa8c">&#34;2012-10-17&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">&#34;Statement&#34;</span>: [{
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">&#34;Effect&#34;</span>: <span style="color:#f1fa8c">&#34;Allow&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">&#34;Principal&#34;</span>: {
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">&#34;Federated&#34;</span>: <span style="color:#f1fa8c">&#34;arn:aws:iam::123456789012:oidc-provider/oidc.eks.us-east-1.amazonaws.com/id/EXAMPLE&#34;</span>
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">&#34;Action&#34;</span>: <span style="color:#f1fa8c">&#34;sts:AssumeRoleWithWebIdentity&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">&#34;Condition&#34;</span>: {
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">&#34;StringEquals&#34;</span>: {
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">&#34;oidc.eks.us-east-1.amazonaws.com/id/EXAMPLE:sub&#34;</span>: <span style="color:#f1fa8c">&#34;system:serviceaccount:default:minha-sa&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">&#34;oidc.eks.us-east-1.amazonaws.com/id/EXAMPLE:aud&#34;</span>: <span style="color:#f1fa8c">&#34;sts.amazonaws.com&#34;</span>
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  }]
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Qualquer vírgula fora do lugar no <code>sub</code> e você pega <code>AccessDenied</code> sem pista do porquê.</p>
<h2 id="o-que-mudou-com-pod-identity">O que mudou com Pod Identity</h2>
<p>Pod Identity tira o OIDC da equação. Você instala o <strong>EKS Pod Identity Agent</strong> como addon do cluster, cria uma Role com trust policy pro principal <code>pods.eks.amazonaws.com</code> (não mais federated OIDC), e registra um &ldquo;Pod Identity Association&rdquo; linkando role e Service Account. O agent, que roda como DaemonSet, intercepta as chamadas do SDK e injeta credenciais.</p>
<p>A trust policy simplifica muito:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">&#34;Version&#34;</span>: <span style="color:#f1fa8c">&#34;2012-10-17&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">&#34;Statement&#34;</span>: [{
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">&#34;Effect&#34;</span>: <span style="color:#f1fa8c">&#34;Allow&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">&#34;Principal&#34;</span>: { <span style="color:#ff79c6">&#34;Service&#34;</span>: <span style="color:#f1fa8c">&#34;pods.eks.amazonaws.com&#34;</span> },
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">&#34;Action&#34;</span>: [<span style="color:#f1fa8c">&#34;sts:AssumeRole&#34;</span>, <span style="color:#f1fa8c">&#34;sts:TagSession&#34;</span>]
</span></span><span style="display:flex;"><span>  }]
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Pra criar a associação via CLI:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>aws eks create-pod-identity-association <span style="color:#f1fa8c">\
</span></span></span><span style="display:flex;"><span>  --cluster-name meu-cluster <span style="color:#f1fa8c">\
</span></span></span><span style="display:flex;"><span>  --namespace default <span style="color:#f1fa8c">\
</span></span></span><span style="display:flex;"><span>  --service-account minha-sa <span style="color:#f1fa8c">\
</span></span></span><span style="display:flex;"><span>  --role-arn arn:aws:iam::123456789012:role/minha-role
</span></span></code></pre></div><p>Nenhuma annotation na Service Account. A associação mora no plano de controle do EKS.</p>
<h2 id="diferenças-práticas">Diferenças práticas</h2>
<table>
  <thead>
      <tr>
          <th>Critério</th>
          <th>IRSA</th>
          <th>Pod Identity</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Setup por cluster</td>
          <td>OIDC provider + IAM config</td>
          <td>Instalar addon</td>
      </tr>
      <tr>
          <td>Trust policy por role</td>
          <td>Complexa (OIDC federation)</td>
          <td>Simples (service principal)</td>
      </tr>
      <tr>
          <td>Associação SA → Role</td>
          <td>Annotation na SA</td>
          <td>Associação no plano EKS</td>
      </tr>
      <tr>
          <td>Reuso de role entre clusters</td>
          <td>Trust policy precisa listar cada cluster</td>
          <td>Trust policy única</td>
      </tr>
      <tr>
          <td>Dependência de addon no cluster</td>
          <td>Não</td>
          <td>Sim (Pod Identity Agent)</td>
      </tr>
      <tr>
          <td>Região suportada</td>
          <td>Todas</td>
          <td>Maioria (verificar)</td>
      </tr>
      <tr>
          <td>Maturidade ecosistema</td>
          <td>Alta (todas as ferramentas)</td>
          <td>Crescente</td>
      </tr>
  </tbody>
</table>
<h2 id="o-bug-silencioso">O bug silencioso</h2>
<p>Estava migrando um serviço de IRSA pra Pod Identity num cluster da Harmo criado há mais de um ano. Fiz tudo: nova role, associação via CLI, removi a annotation antiga. Rollout do pod. Resultado:</p>
<pre tabindex="0"><code>AccessDeniedException: User: anonymous is not authorized to perform: secretsmanager:GetSecretValue
</code></pre><p>&ldquo;User: anonymous&rdquo;, ou seja, o pod estava fazendo a chamada sem nenhuma credencial. Não era problema de permissão da role, era problema de não ter role nenhuma injetada.</p>
<p>Checklist que rodei na sequência:</p>
<ul>
<li>Associação existe? Sim (<code>aws eks list-pod-identity-associations</code> confirma).</li>
<li>Trust policy ok? Sim.</li>
<li>Service Account certa no deployment? Sim.</li>
<li>Logs do pod mostram tentativa de chamada? Sim.</li>
</ul>
<p>Travei. Foram uns 40 minutos só de debug ativo em frente ao terminal, e o incidente todo, contando rollback do serviço pra IRSA, voltar pra Pod Identity depois do fix e revalidar, comeu umas duas horas no total. O erro era óbvio quando encontrei: <strong>o EKS Pod Identity Agent não estava instalado no cluster</strong>. Era um cluster antigo, criado antes do Pod Identity existir, e o addon simplesmente não estava lá. Sem o agent rodando como DaemonSet em cada nó, a interceptação não acontece, o SDK não encontra credencial e cai pra modo anonymous.</p>
<p>Solução, uma linha:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>aws eks create-addon <span style="color:#f1fa8c">\
</span></span></span><span style="display:flex;"><span>  --cluster-name meu-cluster <span style="color:#f1fa8c">\
</span></span></span><span style="display:flex;"><span>  --addon-name eks-pod-identity-agent
</span></span></code></pre></div><p>O que piora é que a mensagem de erro da AWS não indica isso. &ldquo;anonymous&rdquo; parece problema de trust, não de infraestrutura ausente. A lição: quando um cluster é antigo e você está adotando um recurso novo do EKS, verifique primeiro se o addon correspondente está instalado.</p>
<p>Pra checar rapidamente:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>kubectl get daemonset -n kube-system eks-pod-identity-agent
</span></span></code></pre></div><p>Se não retornar nada, está faltando.</p>
<h2 id="quando-usar-qual">Quando usar qual</h2>
<p>Recomendação pragmática:</p>
<ul>
<li><strong>Cluster novo, sem legado</strong>: Pod Identity. Setup mais simples, trust policy mais limpa, menos coisas pra errar.</li>
<li><strong>Cluster existente, IRSA funcionando</strong>: não migre só por migrar. Migração dá trabalho e o valor pode pode ser limitado. Migre quando tiver um motivo real (reuso de role entre clusters, simplificar auditoria).</li>
<li><strong>Role compartilhada entre vários clusters</strong>: Pod Identity ganha por muito. Com IRSA, cada OIDC provider precisa aparecer na trust policy, vira lista enorme. Com Pod Identity, uma trust policy serve todos.</li>
<li><strong>Ambiente multi-cloud ou precisa de OIDC issuer externo</strong>: IRSA. Pod Identity é AWS-only.</li>
</ul>
<h2 id="lições">Lições</h2>
<p>IRSA é sólido mas verboso. Trust policy com OIDC funciona bem até você precisar debugar, e qualquer erro de string no <code>sub</code> vira <code>AccessDenied</code> sem pista. Pod Identity simplifica o caminho feliz com menos peças móveis e trust policy legível, mas em troca depende de infraestrutura no cluster: o agent precisa estar rodando como DaemonSet, e se você está migrando cluster antigo, o primeiro passo é checar o addon, não a trust policy.</p>
<p>A lição transversal é mais geral. Erro de <code>AccessDenied</code> no EKS pede checagem de camadas, não só de permissão. &ldquo;User: anonymous&rdquo; não é permissão insuficiente, é credencial ausente, e isso vira problema de infraestrutura até prova em contrário.</p>
<p>Pensa num prédio com catraca controlada por leitor de crachá. Você passa o crachá e a catraca não abre, mostrando &ldquo;acesso negado&rdquo;. O reflexo é pensar que seu crachá perdeu permissão, foi desativado, mudou de departamento, e você vai falar com o RH, com a segurança, gasta uma hora investigando o cadastro. Aí descobre que o leitor de crachá daquele andar nunca foi instalado. O equipamento que faria a leitura simplesmente não está lá, e qualquer crachá apresentado vai cair na mesma mensagem genérica. É exatamente o que acontece sem o Pod Identity Agent: o leitor não existe, ninguém intercepta a credencial, e a AWS responde como se o pod fosse anônimo. A pista estava num andar acima do que eu estava investigando.</p>
<p>Por último, coexistência é viável: serviços com IRSA e outros com Pod Identity convivem no mesmo cluster sem atrito. Não precisa migrar tudo de uma vez, e talvez nem precise migrar.</p>
<p>Pra quem já roda IRSA bem mapeado em produção, qual foi o motivo concreto que te fez (ou te fará) migrar pra Pod Identity? Reuso de role entre clusters, simplificar trust policy, ou alguma dor específica de debugging que ainda não vi? Quero ler esses casos aqui embaixo, esse tipo de história economiza horas pra quem vem depois.</p>
]]></content:encoded><category>eks</category><category>aws</category><category>kubernetes</category><category>iam</category><category>segurança</category></item><item><title>Concorrência e worker pools em Go: o mínimo que você precisa ter na ponta da língua</title><link>https://rifeli.dev/blog/2026-05-27-concorrencia-worker-pools-go/</link><pubDate>Wed, 27 May 2026 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/2026-05-27-concorrencia-worker-pools-go/</guid><description>Goroutines, channels, fan-out/fan-in, worker pool com errgroup e cancelamento via context. Os patterns de concorrência que realmente aparecem em código de produção.</description><content:encoded><![CDATA[<img id="image-custom" src="/images/posts/5ba7c7c0-1ce0-4913-b12e-b004122025ef.png" alt="Diagrama de fan-out e fan-in em Go: uma entrada se divide em cinco linhas paralelas de processamento que voltam a convergir na saída" />
<p id="image-legend">Goroutines, channels, fan-out/fan-in, worker pool com errgroup e cancelamento via context</p>
<h2 id="introdução">Introdução</h2>
<p>Na Harmo, +50 dos nossos microservices em produção rodam em Go. Eles atendem +10 milhões de pesquisas, +1 milhão de eventos de performance de loja e +300 mil avaliações públicas por mês em mais de 60 mil lojas físicas, com picos que estouram facilmente um worker pool mal dimensionado. Quando você opera nesse volume, um único serviço com goroutine sem limite vira gargalo da plataforma inteira em minutos, não em horas.</p>
<p>Também usamos Go pesado em workloads orquestrados pelo nosso Airflow rodando em MWAA, com workers em Go disparados sob demanda pelas DAGs. É uma stack que merece post próprio e vai sair aqui em breve: a topologia inteira, as decisões de orquestração entre o scheduler em Python e os workers em Go, e os gotchas operacionais que aparecem quando se opera DAGs grandes apoiadas em Kubernetes. Por enquanto, fica registrado que o worker pool desse post é a base mental de muito do que roda lá.</p>
<p><code>go func() { ... }()</code> é uma linha. Disparar mil goroutines em um loop é tentador. Depois a conta chega: exaustão de memória, rate limit de API estourado, race condition sutil que só aparece em produção sob carga real.</p>
<p>Esse post é o conjunto mínimo de patterns que eu uso e reviso em code review na Harmo. Não é tutorial de goroutine nem de channel. É o que vira reflexo depois de alguns anos escrevendo Go em produção: worker pool bem feito, fan-out/fan-in com controle de erro, cancelamento via <code>context</code>, e o que mudou com Go 1.25 que vale incorporar agora.</p>
<p>Começamos a usar Go na Harmo em 2017, ainda na versão 1.7. Famosa frase: quando chegamos, era tudo mato haha.</p>
<h2 id="goroutines-mais-é-menos">Goroutines: mais é menos</h2>
<p>O instinto errado é &ldquo;se goroutines são baratas, use muitas&rdquo;. Cada goroutine é barata sim (alguns KB iniciais), mas:</p>
<ul>
<li><strong>Recurso externo não é.</strong> Se você abre 10.000 goroutines fazendo HTTP pra um serviço, a outra ponta te bloqueia (ou vai cair \o/).</li>
<li><strong>Conexão com banco é cara.</strong> Pool de conexões do PostgreSQL raramente é maior que 20 ou 30. 10.000 goroutines esperam uma conexão. Sem concorrência real, só enfileiramento disfarçado.</li>
<li><strong>CPU é finita.</strong> Trabalho CPU-bound não se beneficia de ter mais goroutines que cores disponíveis.</li>
</ul>
<p>A pergunta certa não é &ldquo;como disparar muitas goroutines&rdquo;, é &ldquo;quantas fazem sentido pra esse trabalho, e como limitar a esse número&rdquo;.</p>
<h2 id="worker-pool-o-padrão-básico">Worker pool: o padrão básico</h2>
<p>O worker pool resolve o problema de limitar concorrência. Estrutura:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">func</span> <span style="color:#50fa7b">processItems</span>(items []Item) <span style="color:#8be9fd">error</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">const</span> workers = <span style="color:#bd93f9">10</span>
</span></span><span style="display:flex;"><span>    jobs <span style="color:#ff79c6">:=</span> <span style="color:#8be9fd;font-style:italic">make</span>(<span style="color:#8be9fd;font-style:italic">chan</span> Item)
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">var</span> wg sync.WaitGroup
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#6272a4">// producers</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">for</span> i <span style="color:#ff79c6">:=</span> <span style="color:#bd93f9">0</span>; i &lt; workers; i<span style="color:#ff79c6">++</span> {
</span></span><span style="display:flex;"><span>        wg.<span style="color:#50fa7b">Add</span>(<span style="color:#bd93f9">1</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">go</span> <span style="color:#8be9fd;font-style:italic">func</span>() {
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">defer</span> wg.<span style="color:#50fa7b">Done</span>()
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">for</span> item <span style="color:#ff79c6">:=</span> <span style="color:#ff79c6">range</span> jobs {
</span></span><span style="display:flex;"><span>                <span style="color:#50fa7b">process</span>(item)
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        }()
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#6272a4">// workers</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">for</span> _, item <span style="color:#ff79c6">:=</span> <span style="color:#ff79c6">range</span> items {
</span></span><span style="display:flex;"><span>        jobs <span style="color:#ff79c6">&lt;-</span> item
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">close</span>(jobs)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    wg.<span style="color:#50fa7b">Wait</span>()
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">return</span> <span style="color:#ff79c6">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Três partes:</p>
<ul>
<li><strong>Abre N workers</strong> que consomem de um channel.</li>
<li><strong>Produtor envia trabalho</strong> e fecha o channel quando termina.</li>
<li><strong><code>WaitGroup</code> espera</strong> todos os workers terminarem.</li>
</ul>
<p>Funciona, mas tem dois problemas: não propaga erro, e não cancela se algum worker falhar.</p>
<h2 id="errgroup-o-que-worker-pool-moderno-deveria-ser">errgroup: o que worker pool moderno deveria ser</h2>
<p><code>golang.org/x/sync/errgroup</code> resolve os dois problemas:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#ff79c6">import</span> <span style="color:#f1fa8c">&#34;golang.org/x/sync/errgroup&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">func</span> <span style="color:#50fa7b">processItems</span>(ctx context.Context, items []Item) <span style="color:#8be9fd">error</span> {
</span></span><span style="display:flex;"><span>    g, ctx <span style="color:#ff79c6">:=</span> errgroup.<span style="color:#50fa7b">WithContext</span>(ctx)
</span></span><span style="display:flex;"><span>    g.<span style="color:#50fa7b">SetLimit</span>(<span style="color:#bd93f9">10</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">for</span> _, item <span style="color:#ff79c6">:=</span> <span style="color:#ff79c6">range</span> items {
</span></span><span style="display:flex;"><span>        g.<span style="color:#50fa7b">Go</span>(<span style="color:#8be9fd;font-style:italic">func</span>() <span style="color:#8be9fd">error</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">return</span> <span style="color:#50fa7b">process</span>(ctx, item)
</span></span><span style="display:flex;"><span>        })
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">return</span> g.<span style="color:#50fa7b">Wait</span>()
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>O que isso te dá:</p>
<ul>
<li><strong><code>SetLimit(10)</code></strong> limita goroutines concorrentes. Chegou no limite, <code>g.Go(...)</code> bloqueia até uma terminar.</li>
<li><strong>Primeiro erro cancela o contexto derivado.</strong> Outras goroutines que respeitam <code>ctx</code> param sozinhas.</li>
<li><strong><code>g.Wait()</code> retorna o primeiro erro.</strong> Os demais são ignorados, o que costuma ser o que você quer. Erro é erro, não precisa coletar todos.</li>
</ul>
<p>Se você ainda roda em Go anterior à 1.22, precisa de <code>item := item</code> dentro do loop pra escapar o shadowing clássico do <code>for/range</code> com closure. A partir do 1.22, cada iteração cria variável nova automaticamente e a linha extra deixou de ser necessária.</p>
<h2 id="o-que-mudou-em-go-125">O que mudou em Go 1.25</h2>
<p>Duas mudanças do Go 1.25 entram direto nessa conversa e valem incorporar.</p>
<p>A primeira é <code>sync.WaitGroup.Go(func())</code>. O <code>WaitGroup</code> ganhou um método novo que substitui o trio <code>wg.Add(1)</code> + <code>go func</code> + <code>defer wg.Done()</code> por uma única chamada:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">var</span> wg sync.WaitGroup
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">for</span> i <span style="color:#ff79c6">:=</span> <span style="color:#bd93f9">0</span>; i &lt; workers; i<span style="color:#ff79c6">++</span> {
</span></span><span style="display:flex;"><span>    wg.<span style="color:#50fa7b">Go</span>(<span style="color:#8be9fd;font-style:italic">func</span>() {
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">for</span> item <span style="color:#ff79c6">:=</span> <span style="color:#ff79c6">range</span> jobs {
</span></span><span style="display:flex;"><span>            <span style="color:#50fa7b">process</span>(item)
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    })
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>wg.<span style="color:#50fa7b">Wait</span>()
</span></span></code></pre></div><p>Mesmo comportamento do exemplo da seção de worker pool, três linhas a menos por chamada, e uma classe inteira de bug eliminada (esquecer o <code>Add</code> antes da goroutine, ou colocar dentro dela, que é o caso clássico de race entre <code>Add</code> e <code>Wait</code>). Se você usa <code>WaitGroup</code> cru sem propagação de erro, <code>wg.Go(...)</code> é o novo default. Pra fan-out com erro, <code>errgroup</code> continua sendo o caminho.</p>
<p>Por baixo dos panos, o <code>wg.Go(f)</code> é equivalente a três linhas que você já escreveu mil vezes na vida \o/:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">func</span> (wg <span style="color:#ff79c6">*</span>WaitGroup) <span style="color:#50fa7b">Go</span>(f <span style="color:#8be9fd;font-style:italic">func</span>()) {
</span></span><span style="display:flex;"><span>    wg.<span style="color:#50fa7b">Add</span>(<span style="color:#bd93f9">1</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">go</span> <span style="color:#8be9fd;font-style:italic">func</span>() {
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">defer</span> wg.<span style="color:#50fa7b">Done</span>()
</span></span><span style="display:flex;"><span>        <span style="color:#50fa7b">f</span>()
</span></span><span style="display:flex;"><span>    }()
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Não tem mágica, é só o idiom canônico empacotado dentro do próprio <code>WaitGroup</code>. O detalhe que importa está na ordem: o <code>Add(1)</code> acontece síncronamente, na mesma goroutine que chamou <code>Go</code>, antes do <code>go func()</code> que faz o spawn. Isso é o que elimina o race condition clássico (eu pelo menos, já esqueci muito de implementar certo haha).</p>
<p>No antigo, quando alguém colocava <code>wg.Add(1)</code> dentro da goroutine (em vez de antes), abria janela pro produtor chamar <code>wg.Wait()</code> antes da goroutine começar a executar. <code>Wait()</code> retornava imediatamente porque o contador ainda estava em zero, e o trabalho rodava sem nenhuma barreira de sincronização esperando por ele. Bug invisível em laptop, problema sério sob carga, daqueles que só aparecem quando você precisa explicar pro time por que o teste de integração passa mas a feature falha em staging.</p>
<p>O <code>defer wg.Done()</code> dentro da goroutine garante o decremento mesmo se <code>f</code> panicar, mantendo o <code>WaitGroup</code> consistente, e o panic propaga normalmente pra cima. E como <code>wg.Go</code> aceita só <code>func()</code>, não há captura de erro nem de retorno: pra trabalho com falha possível, <code>errgroup</code> continua sendo o caminho. A escolha de design é coerente com o resto do pacote <code>sync</code>: oferece primitivo baixo-nível correto, deixa abstrações de alto-nível (erro, contexto) pro <code>errgroup</code> e amigos.</p>
<p>A segunda mudança é mais profunda e operacional: o runtime agora respeita limites de CPU de cgroups ao definir <code>GOMAXPROCS</code> automaticamente. Antes do 1.25, um pod com limite de 2 CPUs rodando numa máquina física de 64 cores fazia o Go assumir <code>GOMAXPROCS=64</code>. Resultado: scheduler do Go competindo desnecessariamente com o cgroup do Linux pelo CPU real do pod, latência inflada sem causa aparente, contenção de scheduler invisível pro APM.</p>
<p>No EKS, isso sempre foi armadilha clássica. A mitigação até 1.24 era importar <code>go.uber.org/automaxprocs</code> em todo serviço Go pra ajustar manualmente na inicialização. A partir do 1.25, vira comportamento default do runtime. Quem opera em container ganha latência de graça só atualizando a versão. Vale o esforço de migração só por esse item.</p>
<p>E uma terceira que vale citar mesmo não sendo de concorrência direta: o pacote <code>testing/synctest</code> saiu de experimental e virou estável. Permite testar código concorrente de forma determinística, sem flakiness de teste que falha 1 em 100 execuções. Substitui boa parte dos <code>time.Sleep</code> feios que a gente colocava em teste de goroutine pra dar tempo da coisa rodar. Pelo menos aqui, fizemos festa por conta disso haha.</p>
<h2 id="fan-out--fan-in-quando-tem-transformação">Fan-out / fan-in: quando tem transformação</h2>
<p>Worker pool é bom pra processar items independentes. Fan-out/fan-in é pra pipeline: pegar entrada, transformar em paralelo, juntar resultados.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">func</span> <span style="color:#50fa7b">fanOutFanIn</span>(ctx context.Context, ids []<span style="color:#8be9fd">string</span>) ([]Result, <span style="color:#8be9fd">error</span>) {
</span></span><span style="display:flex;"><span>    in <span style="color:#ff79c6">:=</span> <span style="color:#8be9fd;font-style:italic">make</span>(<span style="color:#8be9fd;font-style:italic">chan</span> <span style="color:#8be9fd">string</span>)
</span></span><span style="display:flex;"><span>    out <span style="color:#ff79c6">:=</span> <span style="color:#8be9fd;font-style:italic">make</span>(<span style="color:#8be9fd;font-style:italic">chan</span> Result)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    g, ctx <span style="color:#ff79c6">:=</span> errgroup.<span style="color:#50fa7b">WithContext</span>(ctx)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    g.<span style="color:#50fa7b">Go</span>(<span style="color:#8be9fd;font-style:italic">func</span>() <span style="color:#8be9fd">error</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">defer</span> <span style="color:#8be9fd;font-style:italic">close</span>(in)
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">for</span> _, id <span style="color:#ff79c6">:=</span> <span style="color:#ff79c6">range</span> ids {
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">select</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">case</span> in <span style="color:#ff79c6">&lt;-</span> id:
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">case</span> <span style="color:#ff79c6">&lt;-</span>ctx.<span style="color:#50fa7b">Done</span>():
</span></span><span style="display:flex;"><span>                <span style="color:#ff79c6">return</span> ctx.<span style="color:#50fa7b">Err</span>()
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">return</span> <span style="color:#ff79c6">nil</span>
</span></span><span style="display:flex;"><span>    })
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">const</span> workers = <span style="color:#bd93f9">10</span>
</span></span><span style="display:flex;"><span>    workerGroup <span style="color:#ff79c6">:=</span> errgroup.Group{}
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">for</span> i <span style="color:#ff79c6">:=</span> <span style="color:#bd93f9">0</span>; i &lt; workers; i<span style="color:#ff79c6">++</span> {
</span></span><span style="display:flex;"><span>        workerGroup.<span style="color:#50fa7b">Go</span>(<span style="color:#8be9fd;font-style:italic">func</span>() <span style="color:#8be9fd">error</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">for</span> id <span style="color:#ff79c6">:=</span> <span style="color:#ff79c6">range</span> in {
</span></span><span style="display:flex;"><span>                result, err <span style="color:#ff79c6">:=</span> <span style="color:#50fa7b">fetch</span>(ctx, id)
</span></span><span style="display:flex;"><span>                <span style="color:#ff79c6">if</span> err <span style="color:#ff79c6">!=</span> <span style="color:#ff79c6">nil</span> {
</span></span><span style="display:flex;"><span>                    <span style="color:#ff79c6">return</span> err
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>                <span style="color:#ff79c6">select</span> {
</span></span><span style="display:flex;"><span>                <span style="color:#ff79c6">case</span> out <span style="color:#ff79c6">&lt;-</span> result:
</span></span><span style="display:flex;"><span>                <span style="color:#ff79c6">case</span> <span style="color:#ff79c6">&lt;-</span>ctx.<span style="color:#50fa7b">Done</span>():
</span></span><span style="display:flex;"><span>                    <span style="color:#ff79c6">return</span> ctx.<span style="color:#50fa7b">Err</span>()
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">return</span> <span style="color:#ff79c6">nil</span>
</span></span><span style="display:flex;"><span>        })
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">go</span> <span style="color:#8be9fd;font-style:italic">func</span>() {
</span></span><span style="display:flex;"><span>        workerGroup.<span style="color:#50fa7b">Wait</span>()
</span></span><span style="display:flex;"><span>        <span style="color:#8be9fd;font-style:italic">close</span>(out)
</span></span><span style="display:flex;"><span>    }()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">var</span> results []Result
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">for</span> r <span style="color:#ff79c6">:=</span> <span style="color:#ff79c6">range</span> out {
</span></span><span style="display:flex;"><span>        results = <span style="color:#8be9fd;font-style:italic">append</span>(results, r)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">if</span> err <span style="color:#ff79c6">:=</span> g.<span style="color:#50fa7b">Wait</span>(); err <span style="color:#ff79c6">!=</span> <span style="color:#ff79c6">nil</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">return</span> <span style="color:#ff79c6">nil</span>, err
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">if</span> err <span style="color:#ff79c6">:=</span> workerGroup.<span style="color:#50fa7b">Wait</span>(); err <span style="color:#ff79c6">!=</span> <span style="color:#ff79c6">nil</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">return</span> <span style="color:#ff79c6">nil</span>, err
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">return</span> results, <span style="color:#ff79c6">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Mais verboso, mas o padrão é claro:</p>
<ul>
<li><strong>Um produtor</strong> coloca IDs no channel <code>in</code>.</li>
<li><strong>Workers consomem</strong> de <code>in</code>, processam, colocam em <code>out</code>.</li>
<li><strong>Goroutine separada</strong> fecha <code>out</code> quando todos os workers terminam.</li>
<li><strong>Leitor principal</strong> drena <code>out</code>.</li>
</ul>
<p>Detalhe crítico: todo envio em channel deve ter um <code>select</code> com <code>ctx.Done()</code>. Sem isso, se o contexto cancela mas um worker está bloqueado tentando enviar em <code>out</code>, a goroutine vaza.</p>
<h2 id="cancelamento-com-context">Cancelamento com context</h2>
<p><code>context.Context</code> é o mecanismo padrão de cancelamento em Go. Qualquer função que faz trabalho não-trivial deve aceitar <code>ctx</code> e respeitar <code>ctx.Done()</code> (mas não vai passar ctx default em cada chamada né).</p>
<p>Padrão correto em loop longo:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">func</span> <span style="color:#50fa7b">doWork</span>(ctx context.Context) <span style="color:#8be9fd">error</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">for</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">select</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">case</span> <span style="color:#ff79c6">&lt;-</span>ctx.<span style="color:#50fa7b">Done</span>():
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">return</span> ctx.<span style="color:#50fa7b">Err</span>()
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">default</span>:
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">if</span> err <span style="color:#ff79c6">:=</span> <span style="color:#50fa7b">step</span>(ctx); err <span style="color:#ff79c6">!=</span> <span style="color:#ff79c6">nil</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">return</span> err
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>O <code>default:</code> no <code>select</code> faz ele não bloquear quando o contexto não está cancelado. O check roda uma vez por iteração, custo desprezível, e garante que um cancelamento externo pare o loop.</p>
<p>Em I/O (HTTP, SQL, Redis), não precisa do check manual. As libs modernas respeitam <code>ctx</code> automaticamente, então o check só aparece em loops CPU-bound longos.</p>
<h2 id="armadilhas-comuns">Armadilhas comuns</h2>
<p><strong>Goroutine leak por send em channel não drenado.</strong> Se você sai de uma função e deixa uma goroutine bloqueada tentando enviar num channel sem leitor, ela vaza pra sempre. <code>defer close()</code> + <code>select</code> com <code>ctx.Done()</code> evita. Pra detectar leaks que já chegaram em produção, <a href="/blog/tooling-nativo-go-produtividade-e-profiling-pprof/"><code>pprof</code></a> (valida esse artigo massa na sequência) é a ferramenta padrão e cobri ela com detalhe num post separado sobre tooling nativo do Go.</p>
<p><strong><code>sync.WaitGroup</code> com <code>Add</code> dentro de goroutine.</strong> O <code>Add</code> tem que acontecer antes da goroutine começar. Dentro dela, existe race entre <code>Add</code> e <code>Wait</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>wg.<span style="color:#50fa7b">Add</span>(<span style="color:#bd93f9">1</span>)
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">go</span> <span style="color:#8be9fd;font-style:italic">func</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">defer</span> wg.<span style="color:#50fa7b">Done</span>()
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">...</span>
</span></span><span style="display:flex;"><span>}()
</span></span></code></pre></div><p>Nunca:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#ff79c6">go</span> <span style="color:#8be9fd;font-style:italic">func</span>() {
</span></span><span style="display:flex;"><span>    wg.<span style="color:#50fa7b">Add</span>(<span style="color:#bd93f9">1</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">defer</span> wg.<span style="color:#50fa7b">Done</span>()
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">...</span>
</span></span><span style="display:flex;"><span>}()
</span></span></code></pre></div><p><strong>Shared state sem mutex.</strong> Se duas goroutines escrevem no mesmo map, você tem race. <code>go run -race</code> detecta. Use <code>sync.Mutex</code>, <code>sync.RWMutex</code>, ou <code>sync.Map</code> quando fizer sentido.</p>
<p><strong>Channel sem buffer em fan-in.</strong> Se o consumidor é mais lento que o produtor, produtores ficam bloqueados. Às vezes é o comportamento que você quer (backpressure). Às vezes não é, e nesses casos buffer pequeno (tamanho do número de workers) costuma ser um bom compromisso.</p>
<h2 id="lições-aprendidas">Lições aprendidas</h2>
<p>Cinco fechamentos que valem internalizar antes de fechar essa aba.</p>
<p>Limite sempre, sem exceção. Worker pool ou <code>errgroup.SetLimit</code>, mas nunca dispare goroutines ilimitadas baseado em input externo. É a primeira gambiarra que vira incidente em produção quando o input chega 10x mais do que você esperava, e a fila a montante decide que hoje é dia de empurrar tudo (vai por mim, isso vai salvar tua vida).</p>
<p><code>errgroup</code> é quase sempre o que você quer. Se você está usando <code>sync.WaitGroup</code> cru pra coordenar trabalho com possibilidade de erro, provavelmente está reescrevendo mal o que o <code>errgroup</code> faz bem. A partir do Go 1.25, o <code>WaitGroup.Go(...)</code> cobre o caso simples sem erro de forma muito mais limpa, mas pra qualquer coisa com propagação, <code>errgroup</code> segue como default.</p>
<p>Cancelamento é contrato, não opção. Se a sua função aceita <code>ctx</code>, precisa respeitar <code>ctx.Done()</code>. Função que faz I/O sem aceitar <code>ctx</code> é sinal de algo mal desenhado, e geralmente quem está no topo da pilha precisa fazer um milagre pra forçar timeout do lado de fora.</p>
<p>Teste com <code>-race</code> sempre. O race detector é um dos melhores recursos do Go. CI que não roda <code>go test -race</code> está perdendo bug invisível que vai aparecer em produção sob carga. No 1.25 ele ganhou companhia útil: <code>testing/synctest</code> permite testes determinísticos de código concorrente. Se você ainda escreve teste de goroutine com <code>time.Sleep</code> esperando a coisa rodar, troca essa abordagem.</p>
<p>Concorrência é técnica, não filosofia. Vai longe mais rápido quem tem quatro ou cinco patterns na ponta da língua e os usa consistentemente em todo lugar. Channel pattern exótico e goroutine com sinal customizado ficam pra quem já passou da regra básica. A maioria do código de produção precisa só do básico bem feito.</p>
<p>Se você está revendo um serviço crítico em Go essa semana, escolhe um worker pool da sua base e roda esse checklist mental:</p>
<ol>
<li>tem limite?</li>
<li>aceita context?</li>
<li>propaga erro com <code>errgroup</code>?</li>
<li>tem teste com <code>-race</code>?</li>
</ol>
<p>Já está no 1.25 pra colher o <strong>GOMAXPROCS</strong> container-aware de graça? Cada &ldquo;não&rdquo; é dívida técnica esperando incidente.</p>
<p>Pra fechar com uma curiosidade que me interessa de verdade: qual armadilha de concorrência em Go você já bateu em produção que não entrou nessa lista? Esse tipo de história, costuma valer mais que tutorial bem escrito. Vou ler os comentários abaixo com caderno em mãos, e provavelmente atualizando essa página com o que aparecer.</p>
<p>Obrigado.</p>
]]></content:encoded><category>golang</category><category>concorrência</category><category>backend</category><category>patterns</category></item><item><title>Batch inserts e ON CONFLICT no PostgreSQL: saindo do single-row em consumers de alta frequência</title><link>https://rifeli.dev/blog/2026-05-25-batch-inserts-on-conflict-postgresql/</link><pubDate>Mon, 25 May 2026 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/2026-05-25-batch-inserts-on-conflict-postgresql/</guid><description>Por que single-row inserts matam performance em alta frequência, como estruturar batch inserts corretamente, e a semântica do ON CONFLICT DO NOTHING vs DO UPDATE, com benchmarks reais.</description><content:encoded><![CDATA[<img id="image-custom" src="/images/posts/1ab616ab-fb3a-44ee-8f01-a97f4bd668c5.png" alt="Duas esteiras alimentando um banco PostgreSQL: acima, inserts individuais com cronômetro em 47 segundos; abaixo, lotes agrupados em 1,4 segundo" />
<p id="image-legend">Single-row vs batch de 500: mesma carga de 10 mil eventos, mesmo schema, mesmo Aurora. 47 segundos contra 1,4.</p>
<h2 id="introdução">Introdução</h2>
<p>Um consumer que lê de fila e grava um registro por vez no PostgreSQL é um erro que só aparece quando dá errado \o/. Em baixa frequência funciona bem, é código simples e cada evento é atômico. Em alta frequência, vira o gargalo do banco inteiro.</p>
<p>No post anterior da série, contei de um incidente no Aurora onde um consumer que escrevia evento-a-evento estava multiplicando a carga de escrita. Aqui desço ao nível de implementação: por que isso dói, como refatorar pra batch com <code>INSERT ... ON CONFLICT</code>, e onde moram as armadilhas.</p>
<p>Pra dar tamanho ao contexto: a Harmo opera mais de 70 microservices em produção, e esse Aurora absorve na média 4.500 operações de escrita por segundo, com picos passando de 24 mil em hora cheia. Quando um único consumer dessa arquitetura grava evento-a-evento, o pedágio fixo do PostgreSQL por <code>INSERT</code> vira a maior parte do trabalho do banco. Single-row em alta frequência não é erro de iniciante. É erro de quem ainda não levou banco ao limite.</p>
<h2 id="por-que-single-row-inserts-matam-em-alta-frequência">Por que single-row inserts matam em alta frequência</h2>
<p>Toda vez que você faz um <code>INSERT</code> único, o PostgreSQL paga um pedágio fixo:</p>
<ul>
<li><strong>Round-trip de rede</strong> entre app e banco</li>
<li><strong>Parse e planning</strong> da query (aliviado por <code>pg_prepare</code> se você usar, mas ainda assim um custo)</li>
<li><strong>Adquirir e liberar lock</strong> no heap e no índice</li>
<li><strong>WAL write</strong> separado pra aquele registro</li>
<li><strong>Commit</strong> individual se você não agrupou em transação</li>
</ul>
<p>Em baixa frequência (dezenas por segundo) esse pedágio é invisível. Quando o consumer chega em alguns milhares de eventos por segundo, ele passa a ser a maior parte do trabalho do banco. O PostgreSQL gasta mais energia na cerimônia do insert do que gravando dado útil.</p>
<p>Um lote de 500 registros em um único <code>INSERT</code> paga esse pedágio <strong>uma vez</strong>. O <code>INSERT</code> em si fica maior, claro, mas a razão custo-fixo/trabalho-útil melhora drasticamente.</p>
<h2 id="estruturando-o-batch-insert">Estruturando o batch insert</h2>
<p>Duas formas de montar um batch no PostgreSQL:</p>
<p><strong>Forma 1: VALUES com múltiplas linhas</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#ff79c6">INSERT</span> <span style="color:#ff79c6">INTO</span> events (tenant_id, event_type, payload, received_at)
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">VALUES</span>
</span></span><span style="display:flex;"><span>  ($<span style="color:#bd93f9">1</span>, $<span style="color:#bd93f9">2</span>, $<span style="color:#bd93f9">3</span>, $<span style="color:#bd93f9">4</span>),
</span></span><span style="display:flex;"><span>  ($<span style="color:#bd93f9">5</span>, $<span style="color:#bd93f9">6</span>, $<span style="color:#bd93f9">7</span>, $<span style="color:#bd93f9">8</span>),
</span></span><span style="display:flex;"><span>  ($<span style="color:#bd93f9">9</span>, $<span style="color:#bd93f9">10</span>, $<span style="color:#bd93f9">11</span>, $<span style="color:#bd93f9">12</span>);
</span></span></code></pre></div><p>Simples, legível, funciona. Desvantagem: se você precisa parametrizar 500 linhas com 4 colunas cada, são 2000 placeholders. O driver do cliente começa a reclamar, e o limite máximo de parâmetros do PostgreSQL é 65.535. Viável pra batches médios, mas não é a forma mais limpa.</p>
<p><strong>Forma 2: UNNEST de arrays</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#ff79c6">INSERT</span> <span style="color:#ff79c6">INTO</span> events (tenant_id, event_type, payload, received_at)
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">SELECT</span> <span style="color:#ff79c6">*</span> <span style="color:#ff79c6">FROM</span> <span style="color:#ff79c6">unnest</span>(
</span></span><span style="display:flex;"><span>  $<span style="color:#bd93f9">1</span>::uuid[],
</span></span><span style="display:flex;"><span>  $<span style="color:#bd93f9">2</span>::<span style="color:#8be9fd;font-style:italic">text</span>[],
</span></span><span style="display:flex;"><span>  $<span style="color:#bd93f9">3</span>::jsonb[],
</span></span><span style="display:flex;"><span>  $<span style="color:#bd93f9">4</span>::timestamptz[]
</span></span><span style="display:flex;"><span>);
</span></span></code></pre></div><p>Aqui você passa 4 parâmetros, independente do tamanho do batch — cada um é um array. O driver serializa tudo em uma única chamada. É mais eficiente pra batches grandes e é a forma que a gente preferiu na refatoração.</p>
<p>Em Go, usando <code>pgx</code>, fica assim:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">type</span> Event <span style="color:#8be9fd;font-style:italic">struct</span> {
</span></span><span style="display:flex;"><span>    EventID    uuid.UUID
</span></span><span style="display:flex;"><span>    TenantID   uuid.UUID
</span></span><span style="display:flex;"><span>    Type       <span style="color:#8be9fd">string</span>
</span></span><span style="display:flex;"><span>    Payload    <span style="color:#8be9fd;font-style:italic">map</span>[<span style="color:#8be9fd">string</span>]<span style="color:#8be9fd">any</span>
</span></span><span style="display:flex;"><span>    ReceivedAt time.Time
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">func</span> <span style="color:#50fa7b">InsertEvents</span>(ctx context.Context, db DBTX, events []Event) <span style="color:#8be9fd">error</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">if</span> <span style="color:#8be9fd;font-style:italic">len</span>(events) <span style="color:#ff79c6">==</span> <span style="color:#bd93f9">0</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">return</span> <span style="color:#ff79c6">nil</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    tenantIDs <span style="color:#ff79c6">:=</span> <span style="color:#8be9fd;font-style:italic">make</span>([]uuid.UUID, <span style="color:#8be9fd;font-style:italic">len</span>(events))
</span></span><span style="display:flex;"><span>    types <span style="color:#ff79c6">:=</span> <span style="color:#8be9fd;font-style:italic">make</span>([]<span style="color:#8be9fd">string</span>, <span style="color:#8be9fd;font-style:italic">len</span>(events))
</span></span><span style="display:flex;"><span>    payloads <span style="color:#ff79c6">:=</span> <span style="color:#8be9fd;font-style:italic">make</span>([]<span style="color:#8be9fd">string</span>, <span style="color:#8be9fd;font-style:italic">len</span>(events))
</span></span><span style="display:flex;"><span>    receivedAts <span style="color:#ff79c6">:=</span> <span style="color:#8be9fd;font-style:italic">make</span>([]time.Time, <span style="color:#8be9fd;font-style:italic">len</span>(events))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">for</span> i, e <span style="color:#ff79c6">:=</span> <span style="color:#ff79c6">range</span> events {
</span></span><span style="display:flex;"><span>        b, err <span style="color:#ff79c6">:=</span> json.<span style="color:#50fa7b">Marshal</span>(e.Payload)
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">if</span> err <span style="color:#ff79c6">!=</span> <span style="color:#ff79c6">nil</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">return</span> fmt.<span style="color:#50fa7b">Errorf</span>(<span style="color:#f1fa8c">&#34;marshal payload event %d: %w&#34;</span>, i, err)
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        tenantIDs[i] = e.TenantID
</span></span><span style="display:flex;"><span>        types[i] = e.Type
</span></span><span style="display:flex;"><span>        payloads[i] = <span style="color:#8be9fd;font-style:italic">string</span>(b)
</span></span><span style="display:flex;"><span>        receivedAts[i] = e.ReceivedAt
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    _, err <span style="color:#ff79c6">:=</span> db.<span style="color:#50fa7b">Exec</span>(ctx, <span style="color:#f1fa8c">`
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">        INSERT INTO events (tenant_id, event_type, payload, received_at)
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">        SELECT tenant_id, event_type, payload::jsonb, received_at
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">        FROM unnest($1::uuid[], $2::text[], $3::text[], $4::timestamptz[])
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">          AS u(tenant_id, event_type, payload, received_at)
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">    `</span>, tenantIDs, types, payloads, receivedAts)
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">if</span> err <span style="color:#ff79c6">!=</span> <span style="color:#ff79c6">nil</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">return</span> fmt.<span style="color:#50fa7b">Errorf</span>(<span style="color:#f1fa8c">&#34;bulk insert events: %w&#34;</span>, err)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">return</span> <span style="color:#ff79c6">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Três detalhes que valem comentar. Primeiro, <code>DBTX</code> é uma interface mínima que aceita <code>*pgx.Conn</code>, <code>*pgxpool.Pool</code> e <code>pgx.Tx</code>, você passa qualquer um sem mudar a assinatura, e testar a função com transação fica trivial. Segundo, o cast <code>payload::jsonb</code> no <code>SELECT</code>: array de <code>jsonb</code> direto no <code>pgx</code> ainda dá fricção de encoding em algumas versões, então passar JSON como <code>text[]</code> e converter no SELECT funciona em qualquer setup, sem registrar tipo custom. Terceiro, o guard de <code>len(events) == 0</code> evita uma query desnecessária e uma mensagem de erro pouco amigável do <code>pgx</code> em array vazio.</p>
<h2 id="on-conflict-do-nothing-vs-do-update">ON CONFLICT: DO NOTHING vs DO UPDATE</h2>
<p>Quando o consumer pode receber eventos duplicados (e numa fila à-la-SQS com at-least-once, sempre pode), você precisa decidir o que fazer em colisão de chave única.</p>
<p><strong>DO NOTHING</strong> — ignora silenciosamente:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#ff79c6">INSERT</span> <span style="color:#ff79c6">INTO</span> events (event_id, tenant_id, payload)
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">VALUES</span> ($<span style="color:#bd93f9">1</span>, $<span style="color:#bd93f9">2</span>, $<span style="color:#bd93f9">3</span>)
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">ON</span> CONFLICT (event_id) <span style="color:#ff79c6">DO</span> <span style="color:#ff79c6">NOTHING</span>;
</span></span></code></pre></div><p>Uso: quando o dado mais novo não vale mais que o mais velho. Eventos imutáveis, logs append-only, eventos idempotentes. <strong>Na maioria dos consumers de fila, é isso que você quer.</strong></p>
<p><strong>DO UPDATE</strong> — faz upsert:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#ff79c6">INSERT</span> <span style="color:#ff79c6">INTO</span> events (event_id, name, updated_at)
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">VALUES</span> ($<span style="color:#bd93f9">1</span>, $<span style="color:#bd93f9">2</span>, now())
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">ON</span> CONFLICT (event_id) <span style="color:#ff79c6">DO</span> <span style="color:#ff79c6">UPDATE</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">SET</span> name <span style="color:#ff79c6">=</span> EXCLUDED.name,
</span></span><span style="display:flex;"><span>      updated_at <span style="color:#ff79c6">=</span> EXCLUDED.updated_at
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">WHERE</span> events.updated_at <span style="color:#ff79c6">&lt;</span> EXCLUDED.updated_at;
</span></span></code></pre></div><p>Uso: quando o registro mais novo sobrepõe o antigo. Note a cláusula <code>WHERE</code> na ponta, é o que garante que um evento atrasado não sobrescreva um evento mais recente que já chegou antes. Sem isso, você cria inconsistência temporal.</p>
<p>O <code>EXCLUDED</code> é uma pseudo-tabela que representa a linha que tentou entrar no <code>INSERT</code>. Fundamental pra diferenciar &ldquo;o que está no banco&rdquo; de &ldquo;o que eu estou tentando gravar&rdquo;.</p>
<h2 id="armadilha-on-conflict-em-batch-com-duplicatas-dentro-do-próprio-batch">Armadilha: ON CONFLICT em batch com duplicatas dentro do próprio batch</h2>
<p>Essa pegou a gente de surpresa. Em um batch de 500 eventos, o consumer pode ter 2 ou 3 com a mesma chave (a fila entregou duplicatas no mesmo pull). O <code>ON CONFLICT</code> só age contra o que já está no heap, dentro do batch, ele não ajuda. O resultado é um erro:</p>
<pre tabindex="0"><code>ERROR: ON CONFLICT DO UPDATE command cannot affect row a second time
</code></pre><p>Solução: deduplicate no cliente antes de mandar pro banco.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>seen <span style="color:#ff79c6">:=</span> <span style="color:#8be9fd;font-style:italic">make</span>(<span style="color:#8be9fd;font-style:italic">map</span>[uuid.UUID]<span style="color:#8be9fd;font-style:italic">struct</span>{}, <span style="color:#8be9fd;font-style:italic">len</span>(events))
</span></span><span style="display:flex;"><span>deduped <span style="color:#ff79c6">:=</span> <span style="color:#8be9fd;font-style:italic">make</span>([]Event, <span style="color:#bd93f9">0</span>, <span style="color:#8be9fd;font-style:italic">len</span>(events))
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">for</span> _, e <span style="color:#ff79c6">:=</span> <span style="color:#ff79c6">range</span> events {
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">if</span> _, ok <span style="color:#ff79c6">:=</span> seen[e.EventID]; ok {
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">continue</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    seen[e.EventID] = <span style="color:#8be9fd;font-style:italic">struct</span>{}{}
</span></span><span style="display:flex;"><span>    deduped = <span style="color:#8be9fd;font-style:italic">append</span>(deduped, e)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Barato, defensivo, resolve.</p>
<h2 id="benchmarks">Benchmarks</h2>
<p>Teste sintético, mesma carga total de 10.000 eventos, mesmo schema, mesma tabela com 50M registros, Aurora PostgreSQL. Três cenários:</p>
<table>
  <thead>
      <tr>
          <th>Estratégia</th>
          <th>Tempo total</th>
          <th>Inserts/seg</th>
          <th>CPU writer</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Single-row INSERT</td>
          <td>47s</td>
          <td>~213</td>
          <td>78%</td>
      </tr>
      <tr>
          <td>Batch de 100 (UNNEST + ON CONFLICT)</td>
          <td>3.1s</td>
          <td>~3.225</td>
          <td>41%</td>
      </tr>
      <tr>
          <td>Batch de 500 (UNNEST + ON CONFLICT)</td>
          <td>1.4s</td>
          <td>~7.143</td>
          <td>34%</td>
      </tr>
  </tbody>
</table>
<p>O salto de single-row pra batch de 100 é o que mais pesa. De 100 pra 500 o retorno é decrescente: você ganha throughput, mas aumenta o tamanho de lock e o custo de erro (se o batch falhar, 500 eventos precisam ser reprocessados).</p>
<h2 id="qual-batch-size-escolher">Qual batch size escolher</h2>
<p>Sem regra universal, mas orientação prática. Lotes de 50 a 100 são conservadores e cabem bem em tabelas com muita contenção de lock. Entre 200 e 500 fica a faixa que a maioria dos consumers acerta sem precisar pensar muito, e é o que a gente roda em produção. Acima de 1000, só se a tabela é dedicada, sem contenção, e você tolera latência de processamento maior.</p>
<p>Batch size é troca entre throughput e latência. Um consumer que junta 500 eventos antes de gravar tem p99 pior que um que grava de 50 em 50. Se seu caso é &ldquo;realtime&rdquo;, isso pesa.</p>
<p>Falando em latência: nem mesmo o Sol é realtime. Se ele &ldquo;sumir&rdquo; agora, a gente só perceberia daqui a 8 minutos e 20 segundos, que é o tempo que a luz leva pra cruzar os 150 milhões de quilômetros até a Terra. E não é só a luz. Pela relatividade geral, o efeito gravitacional também viaja na velocidade da luz, então a Terra continuaria seguindo a órbita atual nesses mesmos 8 minutos antes de &ldquo;sentir&rdquo; o sumiço e sair em linha reta.</p>
<p>A moral pro nosso problema é a mesma. A única coisa instantânea em qualquer sistema é a parte que cabe num único ciclo de CPU. Tudo que cruza barreira, rede, disco ou escala, paga latência. Quando você projeta um consumer pra batch de 500 em vez de single-row, está aceitando explicitamente o que sistemas distribuídos te impõem implicitamente. Realtime, no sentido estrito, é uma promessa que nem a física faz.</p>
<h2 id="lições-aprendidas">Lições aprendidas</h2>
<ul>
<li><strong>Nunca nasça em single-row.</strong> Qualquer consumer de fila deveria ter batch desde o dia um. É mais código, mas é o código certo.</li>
<li><strong>Dedup no cliente antes do batch.</strong> <code>ON CONFLICT</code> não ajuda contra duplicatas internas ao batch.</li>
<li><strong>Escolha conscientemente DO NOTHING vs DO UPDATE.</strong> Os dois são corretos, mas pra problemas diferentes. Se você está em dúvida, <code>DO NOTHING</code> é o default mais seguro.</li>
<li><strong>Na cláusula de UPDATE, proteja contra out-of-order.</strong> Inclua um <code>WHERE</code> que compara timestamp. Sem isso, evento atrasado sobrescreve evento recente, e você vai descobrir isso da pior forma.</li>
<li><strong>Benchmark no seu ambiente.</strong> Os números acima servem pra ordem de grandeza. Tempo absoluto depende de schema, índices, concorrência e instância.</li>
</ul>
<p>DO NOTHING ou DO UPDATE como default no seu time? Tenho visto muito reflexo de sempre fazer upsert e pouca análise da semântica antes da escolha. Curioso pra saber se outras pessoas se incomodam com isso também.</p>
]]></content:encoded><category>postgresql</category><category>performance</category><category>batch</category><category>sql</category></item><item><title>Política de Privacidade</title><link>https://rifeli.dev/privacidade/</link><pubDate>Mon, 25 May 2026 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/privacidade/</guid><description>Política de privacidade do rifeli.dev: o que coletamos, por quê, e seus direitos sob a LGPD.</description><content:encoded><![CDATA[<p>Esse site (rifeli.dev) é um blog técnico pessoal. A política abaixo explica o que coletamos e por quê.</p>
<h1 id="o-que-coletamos">O que coletamos</h1>
<p>Quando você aceita os cookies de analytics no banner, usamos <strong>Google Analytics 4</strong> com <strong>IP anonimizado</strong> para registrar:</p>
<ul>
<li>Páginas que você visita</li>
<li>Tempo aproximado em cada página</li>
<li>Tipo de dispositivo (mobile/desktop) e navegador</li>
<li>Origem do tráfego (Google, LinkedIn, link direto, etc.)</li>
<li>Localização aproximada (cidade e país, a partir do IP anonimizado)</li>
</ul>
<p>Não coletamos seu nome, e-mail, telefone, nem qualquer dado que te identifique pessoalmente.</p>
<p>Se você clicar em <strong>Recusar</strong> no banner, nenhum desses dados é coletado.</p>
<h1 id="o-que-não-coletamos">O que NÃO coletamos</h1>
<ul>
<li>Endereço de e-mail</li>
<li>Dados de pagamento (não vendemos nada aqui)</li>
<li>Tracking entre sites</li>
<li>Anúncios personalizados</li>
<li>Sinais avançados do Google Signals</li>
</ul>
<h1 id="comentários">Comentários</h1>
<p>Se você comentar em um post, os dados ficam armazenados pelo provedor de comentários sob a política dele, não sob essa.</p>
<h1 id="seus-direitos-sob-a-lgpd">Seus direitos sob a LGPD</h1>
<p>Pela Lei Geral de Proteção de Dados (Lei 13.709/2018), você tem direito a:</p>
<ul>
<li>Saber o que coletamos sobre você (essa página explica)</li>
<li>Solicitar exclusão dos seus dados</li>
<li>Retirar o consentimento a qualquer momento (limpando cookies/localStorage do site, ou clicando em &ldquo;Recusar&rdquo; no banner em uma próxima visita)</li>
<li>Reclamar à ANPD se entender que houve uso indevido</li>
</ul>
<h1 id="contato">Contato</h1>
<p>Para qualquer questão sobre privacidade: <strong><a href="mailto:leonardo@rifeli.dev">leonardo@rifeli.dev</a></strong>.</p>
<h1 id="mudanças">Mudanças</h1>
<p>Essa política pode mudar. Quando mudar, a data de atualização abaixo é alterada.</p>
<p><em>Última atualização: 25 de maio de 2026.</em></p>
]]></content:encoded></item><item><title>Crise de performance no Aurora PostgreSQL: o que aprendi apagando incêndio ao vivo</title><link>https://rifeli.dev/blog/2026-05-24-crise-performance-aurora-postgresql/</link><pubDate>Sun, 24 May 2026 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/2026-05-24-crise-performance-aurora-postgresql/</guid><description>Um incidente real no Aurora PostgreSQL: load spikes, sequential scans em tabelas grandes, índices mortos e inserts single-row em alta frequência. Como diagnosticamos em minutos e quais ações reverteram o quadro.</description><content:encoded><![CDATA[<img id="image-custom" src="https://i.ytimg.com/vi/l7IpsIzMa6E/maxresdefault.jpg" alt="Miniatura de vídeo do YouTube sobre o Amazon Aurora" />
<p id="image-legend">AWS Aurora</p>
<h2 id="introdução">Introdução</h2>
<p>Ninguém planeja uma crise de performance. Ela chega numa quarta-feira de manhã, quando o dashboard fica vermelho e os alertas começam a apitar. Foi mais ou menos assim que começou uma das crises mais instrutivas que atravessei em um Aurora Cluster PostgreSQL 13.18, e o que quero contar aqui não é o &ldquo;fizemos tudo certo&rdquo;, mas a ordem real em que apaguei o fogo, o que funcionou de primeira e o que eu não tinha mapeado direito antes.</p>
<p>Na Harmo, o core transacional roda em Cluster Aurora PostgreSQL, single writer numa instância <strong>db.r8g.4xlarge</strong> (16 vCPU, 128 GB de RAM), storage I/O-Optimized e uma reader <strong>db.r8g.4xlarge</strong>. A plataforma processa +10 milhões de pesquisas e +300k avaliações públicas por mês, e cada uma toca o banco mais de uma vez: avaliação registrada, pesquisa enviada, pesquisa respondida, e-mail aberto, webhook de fornecedor. Quando esse fluxo degrada, a plataforma inteira sente. Mesmo que raro, com crescimento pode acontecer.</p>
<h2 id="o-sintoma">O sintoma</h2>
<p>O primeiro sinal foi CPU de writer subindo de um patamar de 35–45% pra beirar 90% de forma sustentada. Commit latency acompanhou: o p95 de gravação subiu de dezenas de milissegundos pra mais de um segundo. Connections ativas também: o que costumava ficar em torno de 120 foi pra 380 em 15 minutos. Não usamos proxy na frente do banco. Estamos estudando usarmos <a href="https://github.com/prest/prest"><strong>prest</strong></a>.</p>
<p>Três sintomas simultâneos quase nunca têm uma única causa. Eu sabia disso, mas o instinto inicial foi buscar a bala de prata, provavelmente uma query nova de uma feature recém-deployada. Gastei uns cinco minutos olhando a timeline de deploys antes de aceitar que não era isso e partir pra investigação sistêmica.</p>
<h2 id="primeira-hora-localizando-a-dor">Primeira hora: localizando a dor</h2>
<p>Comecei pelo óbvio:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#ff79c6">SELECT</span> query, calls, total_exec_time, mean_exec_time, <span style="color:#ff79c6">rows</span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">FROM</span> pg_stat_statements
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">ORDER</span> <span style="color:#ff79c6">BY</span> total_exec_time <span style="color:#ff79c6">DESC</span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">LIMIT</span> <span style="color:#bd93f9">20</span>;
</span></span></code></pre></div><p>Três coisas saltaram:</p>
<ol>
<li>Uma tabela grande que fazia <code>INSERT</code> em alta frequência, um registro por vez, disparado por um consumer que processava eventos vindos do SES.</li>
<li>Uma query de leitura recorrente estava em <code>Seq Scan</code> num relacionamento de centenas de milhões de linhas, índice existia, mas o planner não usava.</li>
<li>Vários índices apareciam como ativos mas com zero uso em 30 dias.</li>
</ol>
<p>Pra confirmar a terceira hipótese:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#ff79c6">SELECT</span> schemaname, relname, indexrelname, idx_scan, pg_size_pretty(pg_relation_size(indexrelid)) <span style="color:#ff79c6">AS</span> <span style="color:#ff79c6">size</span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">FROM</span> pg_stat_user_indexes
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">WHERE</span> idx_scan <span style="color:#ff79c6">=</span> <span style="color:#bd93f9">0</span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">ORDER</span> <span style="color:#ff79c6">BY</span> pg_relation_size(indexrelid) <span style="color:#ff79c6">DESC</span>;
</span></span></code></pre></div><p>O resultado: 18 índices com <code>idx_scan = 0</code> nos últimos 30 dias, alguns com mais de 2GB cada. Índices não lidos ainda são escritos em toda operação de <code>INSERT</code>/<code>UPDATE</code>/<code>DELETE</code>. Ou seja: write amplification gratuita.</p>
<h2 id="as-três-causas-se-reforçando">As três causas se reforçando</h2>
<p>Isso não era um problema, eram três problemas compostos:</p>
<ul>
<li><strong>Write amplification</strong> por índices mortos encarecendo cada insert.</li>
<li><strong>Inserts single-row de alta frequência</strong> multiplicando round-trips, parse, planning e WAL por registro.</li>
<li><strong>Sequential scan</strong> recorrente consumindo CPU de leitura enquanto o banco já sofria com escrita.</li>
</ul>
<p>Cada um sozinho seria digerível. Os três juntos explicavam o pico simultâneo de CPU, latência e conexões.</p>
<h2 id="a-ordem-das-ações">A ordem das ações</h2>
<p>Primeira questão foi conter acessos e controles de comunicação com clientes. Depois veio a tentação de uma crise: atacar tudo ao mesmo tempo. Errado. Cada ação precisa ter rollback possível e janela de verificação. Fiz assim:</p>
<p><strong>1. Terminar conexões antigas em idle-in-transaction</strong></p>
<p>Alívio imediato. Tinha dezenas de conexões penduradas há mais de 30 minutos:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#ff79c6">SELECT</span> pg_terminate_backend(pid)
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">FROM</span> pg_stat_activity
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">WHERE</span> <span style="color:#ff79c6">state</span> <span style="color:#ff79c6">=</span> <span style="color:#f1fa8c">&#39;idle in transaction&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">AND</span> now() <span style="color:#ff79c6">-</span> state_change <span style="color:#ff79c6">&gt;</span> <span style="color:#8be9fd;font-style:italic">interval</span> <span style="color:#f1fa8c">&#39;10 minutes&#39;</span>;
</span></span></code></pre></div><p>Isso sozinho tirou 40% das conexões ativas e deu fôlego pra investigar com mais calma.</p>
<p><strong>2. Drop dos 18 índices mortos</strong></p>
<p>Em ambiente de produção quente, <code>DROP INDEX CONCURRENTLY</code> pra evitar lock. Um de cada vez, monitorando. Cada drop reduziu write amplification das tabelas afetadas. CPU começou a ceder.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#ff79c6">DROP</span> <span style="color:#ff79c6">INDEX</span> CONCURRENTLY <span style="color:#ff79c6">IF</span> <span style="color:#ff79c6">EXISTS</span> idx_some_unused_composite;
</span></span></code></pre></div><p><strong>3. Índice cirúrgico na tabela grande</strong></p>
<p>A query de leitura fazia <code>Seq Scan</code> porque o índice existente era em uma coluna sozinha, e o planner optava por varrer. Criei o índice composto certo, também com <code>CONCURRENTLY</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#ff79c6">CREATE</span> <span style="color:#ff79c6">INDEX</span> CONCURRENTLY idx_tablex_namey_lookup
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">ON</span> tablex_namey_flowz (tenant_id, status, created_at <span style="color:#ff79c6">DESC</span>);
</span></span></code></pre></div><p>Depois disso, <code>EXPLAIN ANALYZE</code> da query problemática caiu de 4.2 segundos pra 38 milissegundos.</p>
<p><strong>4. Refatoração do consumer SES pra batch insert com ON CONFLICT</strong></p>
<p>Essa foi a ação que não fiz ao vivo, era mudança de código. Agendamos o deploy pro fim do dia. Mas sem essa parte, o alívio das outras três seria temporário: o volume de writes single-row voltaria a pressionar o banco no próximo pico.</p>
<p>No próximo post dessa série vou detalhar essa refatoração. Resumindo: saímos de <code>INSERT</code> um-a-um pra lotes de 500 com <code>INSERT ... ON CONFLICT DO NOTHING</code>. O throughput subiu em ordem de grandeza.</p>
<h2 id="o-que-mudou-depois">O que mudou depois</h2>
<p>Na primeira hora pós-ações: CPU caiu de 90% pra 55%. Connections ativas voltaram pro patamar normal. Latência de commit caiu em torno de 3x.</p>
<p>Na semana seguinte, com o consumer já em batch insert, a CPU média em horário de pico estabilizou em torno de 25–30%. O mesmo hardware, a mesma carga de negócio, só que sem os três multiplicadores compostos.</p>
<h2 id="lições">Lições</h2>
<ol>
<li>Índice custa no write path. Revisar <code>pg_stat_user_indexes</code> uma vez por trimestre é barato e teria evitado boa parte dessa crise.</li>
<li>Single-row insert é inofensivo em baixa frequência e assassino em alta: consumer que processa fila deveria nascer com batch, e se nasceu single-row, é dívida técnica com data de vencimento.</li>
<li>Seq scan não é necessariamente o vilão, mas em tabela grande quase sempre é, e quando o planner ainda escolhe seq scan com índice disponível, o índice provavelmente não é o certo.</li>
<li>Numa crise, ordem importa: matar conexões penduradas compra tempo, dropar índices reduz write amplification antes de criar novos, e mudança de código fica pro deploy calmo.</li>
<li>E um bom alerta teria detectado tudo isso dias antes do alarme que tocou no dia. O próximo post da série destrincha a refatoração do consumer single-row para batch insert, que foi o que estabilizou o quadro definitivamente.</li>
</ol>
<p>Numa crise como essa, qual ação você atacaria primeiro: matar conexões idle, dropar índice morto ou refatorar o consumer? A ordem que escolhi tem motivo e gostaria de ver runbooks alternativos.</p>
]]></content:encoded><category>postgresql</category><category>aurora</category><category>performance</category><category>incidente</category><category>aws</category></item><item><title>Sincronizando Claude Code entre máquinas: o que a Anthropic ainda não faz por você</title><link>https://rifeli.dev/blog/sincronizando-claude-code-entre-maquinas/</link><pubDate>Wed, 22 Apr 2026 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/sincronizando-claude-code-entre-maquinas/</guid><description>O Claude Code não tem sync nativo entre máquinas. Comparo três abordagens (git + symlinks, chezmoi e Syncthing), mostro o pega das memórias path-encoded e dou uma recomendação pragmática.</description><content:encoded><![CDATA[<h2 id="introdução">Introdução</h2>
<p>Você abre o Claude Code no desktop de trabalho pela primeira vez na semana e percebe que ele não lembra de nada: as skills customizadas que você criou, os slash commands que refinou, as memórias acumuladas sobre cada projeto. O CLI não tem sync nativo do diretório <code>~/.claude/</code>. Cada máquina é uma ilha.</p>
<h2 id="mas-eu-só-preciso-commitar-o-claude-do-projeto-né">&ldquo;Mas eu só preciso commitar o <code>.claude/</code> do projeto, né?&rdquo;</h2>
<p>Essa é a primeira reação de quem lê o título — e ela ignora o problema. Claude Code guarda config em <strong>dois lugares diferentes</strong>, e só um deles é resolvido versionando o <code>.claude/</code> dentro do repo:</p>
<p><strong>Por projeto (<code>&lt;repo&gt;/.claude/</code>)</strong> — mora dentro de cada repositório. Tem o <code>settings.json</code> (compartilhável com o time) e o <code>settings.local.json</code> (permissões pessoais de tool use). Esse caso é fácil: basta versionar o que for compartilhado e adicionar <code>settings.local.json</code> ao <code>.git/info/exclude</code> local se você não quiser mesclar permissões com o time.</p>
<p><strong>Global do usuário (<code>~/.claude/</code>)</strong> — mora no seu home, fora de qualquer repo. Inclui:</p>
<ul>
<li>Skills customizadas (<code>~/.claude/plugins/</code>)</li>
<li>Slash commands customizados (<code>~/.claude/commands/</code>)</li>
<li>Config global do CLI (<code>~/.claude/settings.json</code>)</li>
<li>Memórias de contexto por projeto (<code>~/.claude/projects/&lt;path-encoded&gt;/memory/</code>)</li>
</ul>
<p>Este artigo é sobre o segundo caso. Versionar o <code>.claude/</code> do repo não transfere nenhum dos itens acima, porque eles não vivem dentro do repo — vivem no seu <code>$HOME</code>. E é ali que mora o grosso do trabalho de configuração que você acumula ao longo de meses.</p>
<h2 id="por-que-isso-importa">Por que isso importa</h2>
<p>Na Harmo adotamos o Claude Code como ferramenta default de AI para o time de engenharia, então essa dor de sync entre máquinas aparece no dia a dia e não é hipotética.</p>
<p>Quem usa Claude Code a sério acumula configuração global rapidamente: skills que você escreveu pra adequar o comportamento do agente ao seu stack, slash commands que automatizam fluxos repetidos, memórias construídas por semanas de conversa sobre cada projeto. Recriar isso manualmente numa segunda máquina custa horas e é o tipo de tarefa que ninguém faz — simplesmente se trabalha pior do outro lado.</p>
<p>O problema é agravado por um detalhe pouco documentado: memórias de projeto são indexadas pelo path absoluto do diretório. Se no laptop o projeto está em <code>/Users/rifeli/work/harmo</code> e no desktop em <code>/home/rifeli/projects/harmo</code>, as memórias não se transferem mesmo que você copie o diretório <code>~/.claude/</code> inteiro.</p>
<h2 id="sync-também-é-backup">Sync também é backup</h2>
<p>Mesmo que você use uma máquina só, vale configurar uma das estratégias abaixo. SSD morre, sistema é reinstalado, <code>rm -rf</code> acontece na pasta errada. Perder o <code>~/.claude/</code> significa perder semanas de memória de contexto acumulada sobre cada projeto, todas as permissões ajustadas, skills customizadas que você foi refinando. É o tipo de estado que você nem lembra que construiu até ele sumir.</p>
<p>A diferença entre um backup que salva sua pele e um que não salva é o que você excluiu dele. Um backup completo de <code>~/.claude/</code> inclui <code>sessions/</code>, <code>cache/</code> e <code>history.jsonl</code> — conteúdo pesado, inútil para restauração e que vai fazer você desistir do backup porque leva tempo demais. Os mesmos ignores que você configura para sync servem para backup. Qualquer uma das três abordagens deste artigo te dá backup de graça como efeito colateral.</p>
<h2 id="o-que-é-portável-e-o-que-não-é">O que é portável e o que não é</h2>
<p>O <code>~/.claude/</code> tem conteúdo misturado. Separar o que faz sentido sincar evita dor de cabeça depois. A tabela abaixo está agrupada por escopo — note que só as duas primeiras linhas são o famoso &ldquo;basta commitar no repo&rdquo;:</p>
<p><strong>Escopo por-projeto (dentro do repo):</strong></p>
<table>
  <thead>
      <tr>
          <th>Caminho</th>
          <th>Portável?</th>
          <th>Por quê</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>&lt;repo&gt;/.claude/settings.json</code></td>
          <td>Sim, via git do próprio repo</td>
          <td>Config compartilhada com o time</td>
      </tr>
      <tr>
          <td><code>&lt;repo&gt;/.claude/settings.local.json</code></td>
          <td>Sincar por repo</td>
          <td>Permissões pessoais, ignorar no git do time</td>
      </tr>
  </tbody>
</table>
<p><strong>Escopo global (<code>~/.claude/</code>, fora de qualquer repo):</strong></p>
<table>
  <thead>
      <tr>
          <th>Caminho</th>
          <th>Portável?</th>
          <th>Por quê</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>~/.claude/settings.json</code></td>
          <td>Sincar</td>
          <td>Config global do CLI, estável entre máquinas</td>
      </tr>
      <tr>
          <td><code>~/.claude/plugins/</code></td>
          <td>Sincar</td>
          <td>Skills customizadas</td>
      </tr>
      <tr>
          <td><code>~/.claude/commands/</code></td>
          <td>Sincar</td>
          <td>Slash commands customizados</td>
      </tr>
      <tr>
          <td><code>~/.claude/projects/&lt;path&gt;/memory/</code></td>
          <td>Sincar com cuidado</td>
          <td>Path-encoded, ver seção dedicada</td>
      </tr>
      <tr>
          <td><code>~/.claude/cache/</code></td>
          <td>Nunca</td>
          <td>Dados locais pesados e efêmeros</td>
      </tr>
      <tr>
          <td><code>~/.claude/sessions/</code></td>
          <td>Nunca</td>
          <td>Estado de conversa ativa</td>
      </tr>
      <tr>
          <td><code>~/.claude/history.jsonl</code></td>
          <td>Nunca</td>
          <td>Log local, não faz sentido mesclar</td>
      </tr>
      <tr>
          <td><code>~/.claude/shell-snapshots/</code></td>
          <td>Nunca</td>
          <td>Snapshots de shell específicos da máquina</td>
      </tr>
      <tr>
          <td><code>~/.claude.json</code> (credentials)</td>
          <td>Nunca</td>
          <td>Token expira, risco de vazamento</td>
      </tr>
      <tr>
          <td><code>~/.claude/statsig/</code></td>
          <td>Nunca</td>
          <td>Telemetria/feature flags locais</td>
      </tr>
  </tbody>
</table>
<p>A regra prática: sincar o que você configurou conscientemente, ignorar o que o CLI gerou para si próprio. E, importante, o problema deste artigo é o segundo grupo — o primeiro já é resolvido pelo fluxo normal de git do repo.</p>
<h2 id="três-abordagens">Três abordagens</h2>
<h3 id="1-git-repo-privado--symlinks">1. Git repo privado + symlinks</h3>
<p>A solução mais simples e auditável. Funciona bem se você não precisa de sync em tempo real e gosta de controlar o que vai versionado.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#6272a4"># Na primeira máquina: criar o repo</span>
</span></span><span style="display:flex;"><span>mkdir -p ~/dotfiles/claude
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">cd</span> ~/dotfiles
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>git init
</span></span><span style="display:flex;"><span>cat &gt; .gitignore <span style="color:#f1fa8c">&lt;&lt;&#39;EOF&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">claude/cache/
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">claude/sessions/
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">claude/history.jsonl
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">claude/shell-snapshots/
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">claude/statsig/
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">claude/.credentials.json
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">claude/todos/
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">EOF</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># Mover os arquivos reais para o repo e deixar symlinks no lugar</span>
</span></span><span style="display:flex;"><span>mv ~/.claude/settings.json       ~/dotfiles/claude/settings.json
</span></span><span style="display:flex;"><span>mv ~/.claude/plugins             ~/dotfiles/claude/plugins
</span></span><span style="display:flex;"><span>mv ~/.claude/commands            ~/dotfiles/claude/commands 2&gt;/dev/null <span style="color:#ff79c6">||</span> <span style="color:#8be9fd;font-style:italic">true</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>ln -s ~/dotfiles/claude/settings.json ~/.claude/settings.json
</span></span><span style="display:flex;"><span>ln -s ~/dotfiles/claude/plugins       ~/.claude/plugins
</span></span><span style="display:flex;"><span>ln -s ~/dotfiles/claude/commands      ~/.claude/commands
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>git add . <span style="color:#ff79c6">&amp;&amp;</span> git commit -m <span style="color:#f1fa8c">&#34;initial claude config&#34;</span>
</span></span><span style="display:flex;"><span>git remote add origin git@github.com:seu-user/dotfiles.git
</span></span><span style="display:flex;"><span>git push -u origin main
</span></span></code></pre></div><p>Na segunda máquina:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git clone git@github.com:seu-user/dotfiles.git ~/dotfiles
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># Fazer backup de qualquer config existente antes de linkar</span>
</span></span><span style="display:flex;"><span>mv ~/.claude/settings.json ~/.claude/settings.json.bak 2&gt;/dev/null
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>ln -s ~/dotfiles/claude/settings.json ~/.claude/settings.json
</span></span><span style="display:flex;"><span>ln -s ~/dotfiles/claude/plugins       ~/.claude/plugins
</span></span><span style="display:flex;"><span>ln -s ~/dotfiles/claude/commands      ~/.claude/commands
</span></span></code></pre></div><p><strong>Vantagens:</strong> histórico completo, rollback trivial, code review antes de propagar mudanças.
<strong>Desvantagem:</strong> você precisa lembrar de <code>git pull</code> e <code>git push</code>. Não é automático.</p>
<p>Para o <code>settings.local.json</code> de cada repo, a abordagem é diferente — esse arquivo mora dentro do projeto e é gitignored por padrão. Se você confia no time, basta adicionar <code>.claude/settings.local.json</code> ao <code>.git/info/exclude</code> local e versionar <code>.claude/settings.json</code> (versão compartilhada) no repo principal.</p>
<h3 id="2-chezmoi-ou-yadm-com-suporte-a-segredos">2. chezmoi (ou yadm) com suporte a segredos</h3>
<p>O <code>chezmoi</code> resolve dois problemas que o git puro não trata bem: templates por máquina (o path do home no macOS é <code>/Users/rifeli</code>, no Linux é <code>/home/rifeli</code>) e segredos encriptados.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#6272a4"># Instalar chezmoi</span>
</span></span><span style="display:flex;"><span>sh -c <span style="color:#f1fa8c">&#34;</span><span style="color:#ff79c6">$(</span>curl -fsLS get.chezmoi.io<span style="color:#ff79c6">)</span><span style="color:#f1fa8c">&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>chezmoi init
</span></span><span style="display:flex;"><span>chezmoi add ~/.claude/settings.json
</span></span><span style="display:flex;"><span>chezmoi add ~/.claude/plugins
</span></span><span style="display:flex;"><span>chezmoi add ~/.claude/commands
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># Editar o arquivo de ignorar no source state</span>
</span></span><span style="display:flex;"><span>chezmoi edit-config
</span></span></code></pre></div><p>Adicione ao <code>~/.local/share/chezmoi/.chezmoiignore</code>:</p>
<pre tabindex="0"><code>.claude/cache
.claude/sessions
.claude/history.jsonl
.claude/shell-snapshots
.claude/statsig
.claude/.credentials.json
.claude/todos
</code></pre><p>Para config que varia entre máquinas, transforme o arquivo em template. Renomeie <code>dot_claude/settings.json</code> para <code>dot_claude/settings.json.tmpl</code> e use:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">&#34;model&#34;</span>: <span style="color:#f1fa8c">&#34;sonnet&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">&#34;env&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">&#34;EDITOR&#34;</span>: <span style="color:#f1fa8c">&#34;{{ if eq .chezmoi.os \&#34;darwin\&#34; }}cursor{{ else }}code{{ end }}&#34;</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Na segunda máquina:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sh -c <span style="color:#f1fa8c">&#34;</span><span style="color:#ff79c6">$(</span>curl -fsLS get.chezmoi.io<span style="color:#ff79c6">)</span><span style="color:#f1fa8c">&#34;</span> -- init --apply seu-user
</span></span></code></pre></div><p>Se você tem chaves que precisam viajar junto (API keys de outras ferramentas referenciadas no settings), integre com <code>age</code> ou <code>gpg</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>chezmoi add --encrypt ~/.claude/some-secret-config
</span></span></code></pre></div><p><strong>Vantagens:</strong> template-aware, integração nativa com secret managers, um comando para aplicar tudo.
<strong>Desvantagem:</strong> curva de aprendizado maior que git puro; se você nunca usou, é uma ferramenta nova para dominar só para isso.</p>
<h3 id="3-syncthing--dropbox--icloud-com-symlinks-seletivos">3. Syncthing / Dropbox / iCloud com symlinks seletivos</h3>
<p>Sync em tempo real, sem <code>git commit</code>. A diferença crítica em relação aos outros: se você não configurar os ignores corretamente, vai sincar cache, sessões ativas, e eventualmente corromper arquivos com escritas concorrentes.</p>
<p>Usando Syncthing como exemplo (mais seguro que Dropbox para arquivos ativamente escritos porque tem versioning e resolução de conflito explícita):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#6272a4"># Criar diretório de sync</span>
</span></span><span style="display:flex;"><span>mkdir -p ~/Sync/claude-config
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># Mover apenas os arquivos seguros</span>
</span></span><span style="display:flex;"><span>mv ~/.claude/settings.json ~/Sync/claude-config/
</span></span><span style="display:flex;"><span>mv ~/.claude/plugins       ~/Sync/claude-config/
</span></span><span style="display:flex;"><span>mv ~/.claude/commands      ~/Sync/claude-config/ 2&gt;/dev/null
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>ln -s ~/Sync/claude-config/settings.json ~/.claude/settings.json
</span></span><span style="display:flex;"><span>ln -s ~/Sync/claude-config/plugins       ~/.claude/plugins
</span></span><span style="display:flex;"><span>ln -s ~/Sync/claude-config/commands      ~/.claude/commands
</span></span></code></pre></div><p>No Syncthing, adicione <code>~/Sync/claude-config</code> como pasta compartilhada entre as máquinas. <strong>Não sincronize o <code>~/.claude/</code> inteiro</strong> — é tentador e vai te morder.</p>
<p>Com Dropbox ou iCloud o princípio é o mesmo, mas evite ativamente:</p>
<ul>
<li><strong>Sincar <code>sessions/</code> ou <code>history.jsonl</code></strong>: o CLI escreve neles durante uso e sync em tempo real gera conflitos.</li>
<li><strong>Sincar <code>cache/</code></strong>: pode ficar pesado e gerar upload infinito.</li>
<li><strong>Confiar na resolução automática de conflitos</strong>: Dropbox cria arquivo <code>(conflicted copy).json</code> silenciosamente, e o Claude Code não sabe o que fazer com isso.</li>
</ul>
<p><strong>Vantagens:</strong> zero ação manual após setup.
<strong>Desvantagens:</strong> sem histórico acionável, risco alto se a lista de ignore estiver errada, e se as duas máquinas estiverem online simultaneamente e você editar de um lado, o outro lado pode ver estado inconsistente.</p>
<h2 id="o-pega-da-memória-path-encoded">O pega da memória path-encoded</h2>
<p>Memórias vivem em <code>~/.claude/projects/&lt;path-encoded&gt;/memory/</code>, onde <code>&lt;path-encoded&gt;</code> é o path absoluto do projeto com separadores trocados por hífens. Um projeto em <code>/Users/rifeli/work/harmo</code> vira <code>-Users-rifeli-work-harmo</code>. Em <code>/home/rifeli/projects/harmo</code> vira <code>-home-rifeli-projects-harmo</code>. Mesmo repo, duas chaves diferentes, memórias órfãs.</p>
<p>Duas soluções, escolha uma:</p>
<p><strong>Opção A: padronizar o path em todas as máquinas.</strong> A mais limpa. Adote <code>~/projects/&lt;repo&gt;</code> como convenção e crie symlinks se o path real for diferente:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#6272a4"># No desktop, se o repo real está em /mnt/data/work/harmo</span>
</span></span><span style="display:flex;"><span>mkdir -p ~/projects
</span></span><span style="display:flex;"><span>ln -s /mnt/data/work/harmo ~/projects/harmo
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># Sempre abrir o Claude Code via ~/projects/harmo</span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">cd</span> ~/projects/harmo <span style="color:#ff79c6">&amp;&amp;</span> claude
</span></span></code></pre></div><p><strong>Opção B: script de renomeio pós-sync.</strong> Útil quando padronizar não é viável (diferenças de SO, ou times onde cada dev tem layout próprio). Depois de puxar as memórias, renomeie os diretórios:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#ff79c6">#!/bin/bash
</span></span></span><span style="display:flex;"><span><span style="color:#6272a4"># remap-memory.sh — rodar após sync para remapear path-encoded dirs</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">SOURCE_MACHINE_PATH</span><span style="color:#ff79c6">=</span><span style="color:#f1fa8c">&#34;-Users-rifeli-work-harmo&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">TARGET_MACHINE_PATH</span><span style="color:#ff79c6">=</span><span style="color:#f1fa8c">&#34;-home-rifeli-projects-harmo&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">cd</span> ~/.claude/projects
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">if</span> <span style="color:#ff79c6">[</span> -d <span style="color:#f1fa8c">&#34;</span><span style="color:#8be9fd;font-style:italic">$SOURCE_MACHINE_PATH</span><span style="color:#f1fa8c">&#34;</span> <span style="color:#ff79c6">]</span> <span style="color:#ff79c6">&amp;&amp;</span> <span style="color:#ff79c6">[</span> ! -d <span style="color:#f1fa8c">&#34;</span><span style="color:#8be9fd;font-style:italic">$TARGET_MACHINE_PATH</span><span style="color:#f1fa8c">&#34;</span> <span style="color:#ff79c6">]</span>; <span style="color:#ff79c6">then</span>
</span></span><span style="display:flex;"><span>  cp -r <span style="color:#f1fa8c">&#34;</span><span style="color:#8be9fd;font-style:italic">$SOURCE_MACHINE_PATH</span><span style="color:#f1fa8c">&#34;</span> <span style="color:#f1fa8c">&#34;</span><span style="color:#8be9fd;font-style:italic">$TARGET_MACHINE_PATH</span><span style="color:#f1fa8c">&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#8be9fd;font-style:italic">echo</span> <span style="color:#f1fa8c">&#34;Remapped </span><span style="color:#8be9fd;font-style:italic">$SOURCE_MACHINE_PATH</span><span style="color:#f1fa8c"> -&gt; </span><span style="color:#8be9fd;font-style:italic">$TARGET_MACHINE_PATH</span><span style="color:#f1fa8c">&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">fi</span>
</span></span></code></pre></div><p>Dá pra integrar isso como hook do chezmoi (<code>run_after_remap-memory.sh.tmpl</code>) ou como <code>post-merge</code> hook do git no repo de dotfiles.</p>
<h2 id="decisão-rápida">Decisão rápida</h2>
<ul>
<li>Se você quer o mínimo de ferramentas novas e confia em <code>git pull</code>: <strong>git + symlinks</strong>.</li>
<li>Se você sincroniza mais do que só Claude Code (zshrc, nvim, etc) e quer templates por SO: <strong>chezmoi</strong>.</li>
<li>Se você odeia comandar sync manualmente e topa o risco: <strong>Syncthing com ignores rigorosos</strong>. Não Dropbox, não iCloud, por causa do comportamento de conflito.</li>
<li>Se você trabalha em dois SOs diferentes (mac + linux): <strong>chezmoi</strong>, pelo suporte a template.</li>
<li>Se você precisa versionar segredos junto da config: <strong>chezmoi + age</strong>.</li>
</ul>
<h2 id="limitações-honestas">Limitações honestas</h2>
<p>Nada disso resolve:</p>
<ul>
<li><strong>Token de auth.</strong> <code>~/.claude/.credentials.json</code> expira e não vale o risco de espalhar entre máquinas. Faça login em cada uma separadamente.</li>
<li><strong>Sessões ativas.</strong> Se você está no meio de uma conversa no laptop e abre a mesma no desktop, elas não se encontram. Sessão é local.</li>
<li><strong>Cache e downloads de modelo.</strong> Claude Code usa a API; não há modelo local para sincar. Mas se você tem ferramentas que cacheiam respostas, essas caches são por máquina.</li>
<li><strong>Estado de permissões no meio de uma run.</strong> Se você aprovou &ldquo;sempre permitir&rdquo; para uma tool no laptop durante uma sessão e sincou, pode ser que na outra máquina a aprovação só apareça na próxima reinicialização do CLI.</li>
</ul>
<h2 id="comparação-final">Comparação final</h2>
<table>
  <thead>
      <tr>
          <th>Critério</th>
          <th>Git + symlinks</th>
          <th>chezmoi</th>
          <th>Syncthing</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Facilidade de setup</td>
          <td>Alta</td>
          <td>Média</td>
          <td>Média</td>
      </tr>
      <tr>
          <td>Segurança de segredos</td>
          <td>Nenhuma nativa</td>
          <td>age/gpg integrados</td>
          <td>Criptografia de trânsito</td>
      </tr>
      <tr>
          <td>Histórico / rollback</td>
          <td>Completo</td>
          <td>Via git (chezmoi usa git)</td>
          <td>Versioning limitado</td>
      </tr>
      <tr>
          <td>Sync em tempo real</td>
          <td>Não</td>
          <td>Não</td>
          <td>Sim</td>
      </tr>
      <tr>
          <td>Suporte a templates</td>
          <td>Não</td>
          <td>Sim</td>
          <td>Não</td>
      </tr>
      <tr>
          <td>Risco de corromper estado</td>
          <td>Baixo</td>
          <td>Baixo</td>
          <td>Médio-alto</td>
      </tr>
      <tr>
          <td>Curva de aprendizado</td>
          <td>Baixa</td>
          <td>Média</td>
          <td>Baixa</td>
      </tr>
  </tbody>
</table>
<h2 id="conclusão">Conclusão</h2>
<p>A recomendação pragmática para a maioria: <strong>git + symlinks para config estável</strong>, e <strong>padronizar os paths dos projetos</strong> para resolver a questão das memórias. Passa para chezmoi só quando a dor do manual vira maior que a dor de aprender a ferramenta.</p>
<p><strong>💬 E você, como tem sincronizado sua configuração do Claude Code?</strong></p>
<p>Usa dotfiles, sync em nuvem, ou simplesmente aceita a duplicação e reconfigura do zero? Conta nos comentários como você lida com isso no dia a dia.</p>
]]></content:encoded><category>claude-code</category><category>anthropic</category><category>dotfiles</category><category>produtividade</category><category>cli</category></item><item><title>Como começamos com Python para IA e dados na Harmo — do básico ao prático</title><link>https://rifeli.dev/blog/python-ia-dados-harmo-basico-ao-pratico/</link><pubDate>Tue, 03 Mar 2026 01:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/python-ia-dados-harmo-basico-ao-pratico/</guid><description>Python se tornou a principal linguagem para inteligência artificial e ciência de dados. Mas ir além de notebooks e exemplos isolados é o verdadeiro desafio. Neste artigo mostramos como usar Python para construir pipelines de dados, processar grandes volumes de informação e integrar modelos de IA em sistemas reais. A partir de experiências práticas da Harmo, exploramos desde manipulação de dados até APIs de inferência e arquitetura de pipelines para produção.</description><content:encoded><![CDATA[<img id="image-custom" src="https://media.licdn.com/dms/image/v2/D4D22AQGyQghEaoshDw/feedshare-shrink_800/B4DZUzcgftHAAg-/0/1740324866771?e=2147483647&v=beta&t=knlon4niJAJCYOsK-dAKoa3rc0GmWlkpfxm_2baNaSY" alt="cloud-native" />
<p id="image-legend"></p>
<h2 id="introdução">Introdução</h2>
<p>Na Harmo, lidamos com grandes volumes de dados, modelos de IA e pipelines que precisam ser eficientes, escaláveis e fáceis de manter. Python surgiu como a linguagem principal por sua versatilidade, ecossistema maduro e fácil integração com ferramentas de ML/IA, como TensorFlow, PyTorch e LangChain. Utilizamos Python desde 2017 com foco em scrappings, ML e data pipelines (com Spark e EMR).</p>
<p><strong>Neste artigo vamos:</strong></p>
<ul>
<li>Mostrar o porquê de Python ser essencial para IA e dados;</li>
<li>Apresentar conceitos práticos usados no dia a dia da Harmo;</li>
<li>Demonstrar exemplos de código e pipelines reais.</li>
</ul>
<h2 id="-por-que-python-para-ia-e-dados">🧠 Por que Python para IA e Dados</h2>
<p>Python se tornou o padrão para IA e ciência de dados por:</p>
<ol>
<li>Ecossistema rico: NumPy, Pandas, scikit-learn, PyTorch, etc.</li>
<li>Documentação e comunidade com suporte massivo.</li>
<li>Produtização facilitada, APIs e deployment com FastAPI, Docker, etc.</li>
</ol>
<p>Na Harmo, usamos Python em:</p>
<ul>
<li>ETL de dados (extração, transformação e carga);</li>
<li>Treinamento e inferência de modelos;</li>
<li>Orquestração de pipelines (Airflow / Prefect);</li>
<li>APIs de serviço inteligente.</li>
</ul>
<h2 id="-começando-com-python">🔧 Começando com Python</h2>
<p>Antes de tudo, vamos configurar um ambiente de desenvolvimento.</p>
<p>Instalar Python e Virtualenv</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#6272a4"># instalar pyenv (opcional)</span>
</span></span><span style="display:flex;"><span>curl https://pyenv.run | bash
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># instalar Python 3.11</span>
</span></span><span style="display:flex;"><span>pyenv install 3.11.2
</span></span><span style="display:flex;"><span>pyenv <span style="color:#8be9fd;font-style:italic">local</span> 3.11.2
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># criar virtualenv</span>
</span></span><span style="display:flex;"><span>python -m venv .venv
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">source</span> .venv/bin/activate
</span></span></code></pre></div><p>Dependências comuns na Harmo</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>pip install pandas numpy scikit-learn torch transformers fastapi uvicorn
</span></span></code></pre></div><h2 id="-trabalhando-com-dados">📊 Trabalhando com Dados</h2>
<p>Na Harmo, muitos dados vêm de feedback de usuários, logs de uso e integrações externas. O Pandas é nossa ferramenta principal.</p>
<h5 id="exemplo-de-carregamento-e-análise">Exemplo de carregamento e análise</h5>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#ff79c6">import</span> pandas <span style="color:#ff79c6">as</span> pd
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>df <span style="color:#ff79c6">=</span> pd<span style="color:#ff79c6">.</span>read_csv(<span style="color:#f1fa8c">&#34;feedbacks.csv&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">print</span>(df<span style="color:#ff79c6">.</span>head())
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># contar tópicos mais frequentes</span>
</span></span><span style="display:flex;"><span>top_topics <span style="color:#ff79c6">=</span> df[<span style="color:#f1fa8c">&#34;topic&#34;</span>]<span style="color:#ff79c6">.</span>value_counts()<span style="color:#ff79c6">.</span>nlargest(<span style="color:#bd93f9">10</span>)
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">print</span>(top_topics)
</span></span></code></pre></div><h2 id="-ia-prática-em-python">🤖 IA Prática em Python</h2>
<p>Modelos de NLP com Transformers</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#ff79c6">from</span> transformers <span style="color:#ff79c6">import</span> pipeline
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>classifier <span style="color:#ff79c6">=</span> pipeline(<span style="color:#f1fa8c">&#34;sentiment-analysis&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>results <span style="color:#ff79c6">=</span> classifier([
</span></span><span style="display:flex;"><span>    <span style="color:#f1fa8c">&#34;A experiência com o produto foi ótima!&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f1fa8c">&#34;O sistema ficou fora do ar por muito tempo.&#34;</span>
</span></span><span style="display:flex;"><span>])
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">for</span> r <span style="color:#ff79c6">in</span> results:
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">print</span>(r)
</span></span></code></pre></div><p>Esse tipo de análise é usado na Harmo para:</p>
<ul>
<li>entender feedback de clientes;</li>
<li>classificar comentários por sentimento;</li>
<li>alimentar dashboards de decisão.</li>
</ul>
<h2 id="-orquestração-de-pipelines">🚀 Orquestração de Pipelines</h2>
<p>Usamos Airflow ou Prefect para agendar tarefas de dados e IA.</p>
<p>Exemplo (Prefect)</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#ff79c6">from</span> prefect <span style="color:#ff79c6">import</span> flow, task
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>@task
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">def</span> <span style="color:#50fa7b">extract</span>():
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">...</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>@task
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">def</span> <span style="color:#50fa7b">transform</span>(data):
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">...</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>@task
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">def</span> <span style="color:#50fa7b">load</span>(data):
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">...</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>@flow
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">def</span> <span style="color:#50fa7b">etl_pipeline</span>():
</span></span><span style="display:flex;"><span>    data <span style="color:#ff79c6">=</span> extract()
</span></span><span style="display:flex;"><span>    transformed <span style="color:#ff79c6">=</span> transform(data)
</span></span><span style="display:flex;"><span>    load(transformed)
</span></span></code></pre></div><p>PS.: Farei um artigo sobre como utilizar pipelines airflow pra orquestrar pods K8S para tarefas.</p>
<h2 id="-apis-inteligentes-com-fastapi">📡 APIs Inteligentes com FastAPI</h2>
<p>Depois de treinar modelos, a próxima etapa é disponibilizar inferência via API:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#ff79c6">from</span> fastapi <span style="color:#ff79c6">import</span> FastAPI
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">import</span> torch
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>app <span style="color:#ff79c6">=</span> FastAPI()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>@app.post(<span style="color:#f1fa8c">&#34;/predict&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">def</span> <span style="color:#50fa7b">predict</span>(text: <span style="color:#8be9fd;font-style:italic">str</span>):
</span></span><span style="display:flex;"><span>    <span style="color:#6272a4"># chamada do modelo treinado</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">return</span> {<span style="color:#f1fa8c">&#34;prediction&#34;</span>: <span style="color:#f1fa8c">&#34;positivo&#34;</span>}
</span></span></code></pre></div><p>Servimos essas APIs em containers Docker e fazemos deploy com CI/CD.</p>
<h2 id="-testes-e-qualidade">🧪 Testes e Qualidade</h2>
<p>No dia a dia da Harmo, código de dados e IA é coberto por testes unitários:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>pip install pytest
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#ff79c6">def</span> <span style="color:#50fa7b">test_model_output</span>():
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">assert</span> model<span style="color:#ff79c6">.</span>predict(<span style="color:#f1fa8c">&#34;bom&#34;</span>) <span style="color:#ff79c6">==</span> <span style="color:#f1fa8c">&#34;positivo&#34;</span>
</span></span></code></pre></div><h2 id="-métricas-e-monitoramento">📈 Métricas e Monitoramento</h2>
<p>Importante acompanhar:</p>
<ul>
<li>Latência de inferência</li>
<li>Precisão / recall dos modelos</li>
<li>Logs e alertas</li>
</ul>
<p>Usamos ferramentas como:</p>
<ul>
<li>Prometheus</li>
<li>Grafana</li>
<li>Newrelic</li>
</ul>
<h2 id="-lições-aprendidas-na-harmo">🧪 Lições aprendidas na Harmo</h2>
<ol>
<li><strong>Comece simples:</strong> protótipos ajudam validar modelos antes de escalar.</li>
<li><strong>Automatize tudo:</strong> da coleta de dados até o deploy.</li>
<li><strong>Monitore em produção:</strong> sem métricas, não há melhoria.</li>
<li><strong>Código é arte e engenharia:</strong> mantenha legível e testado.</li>
</ol>
<h2 id="-conclusão">🔚 Conclusão</h2>
<p>Python não é apenas uma linguagem, é a base sobre a qual construímos valor em IA e dados na Harmo. Seu ecossistema acelerou nossa entrega de soluções inteligentes e nos permitiu inovar constantemente.</p>
<p>Se você está começando, foque em dominar:</p>
<ul>
<li>pandas/numpy</li>
<li>ML frameworks</li>
<li>APIs e deploy</li>
<li>Testes e automação</li>
</ul>
<p>Python faz a ponte entre dados e impacto real.</p>
]]></content:encoded><category>python</category><category>artificial-intelligence</category><category>llm</category><category>analytics</category><category>data-engineering</category><category>data-science</category></item><item><title>Object Calisthenics em Go: disciplina de design em uma linguagem pragmática</title><link>https://rifeli.dev/blog/object-calisthenics-golang-design/</link><pubDate>Tue, 03 Mar 2026 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/object-calisthenics-golang-design/</guid><description>Eleve a qualidade do seu código Go com as 9 regras do Object Calisthenics. Aprenda a aplicar exercícios de disciplina de design para criar structs coesas, funções limpas e um domínio rico, transformando teoria de Clean Code em prática idiomática no seu dia a dia.</description><content:encoded><![CDATA[<img id="image-custom" src="https://dkrn4sk0rn31v.cloudfront.net/uploads/2022/10/o-que-e-e-como-comecar-com-golang.jpg" alt="cloud-native" />
<p id="image-legend">
  <a href="https://github.com/golang/go" target="_blank">golang/go</a>
</p>
<h2 id="introdução">Introdução</h2>
<p>No meu último artigo, discutimos como Go lida com orientação a objetos através de composição (<a href="https://rifeli.dev/blog/go-e-orientada-a-objetos-paradigmas/">veja aqui</a>). Mas composição sozinha não garante bom design.</p>
<p>Go te dá liberdade: structs simples, métodos livres e interfaces implícitas, porém, é justamente por isso é fácil cair em:</p>
<ul>
<li>Funções gigantes</li>
<li>Services que sabem demais</li>
<li>DTOs anêmicos</li>
<li>Lógica espalhada pelo projeto</li>
</ul>
<p>A pergunta não é &ldquo;Go suporta OO?&rdquo;. Ela precisa ser: Como manter disciplina arquitetural numa linguagem que não impõe nada?</p>
<p>É aqui que entra <strong>Object Calisthenics</strong>.</p>
<h2 id="o-que-é-object-calisthenics">O que é Object Calisthenics?</h2>
<p>Tem uma palestra que marcou minha carreira, é do Guilherme Blanco e foi <a href="https://www.youtube.com/watch?v=u-w4eULRrr0">PHP para Adultos – Object Calisthenics e Clean Code - Guilherme Blanco no InterCon PHP 2014
</a> em set/2014 (entreguei a idade).</p>
<p>Criado por <strong>Jeff Bay</strong>, é um conjunto de 9 regras de design pensadas como treino de musculação para código.</p>
<p>No começo dói.
Depois você começa a enxergar problemas antes deles virarem débito técnico.</p>
<p>Mas aqui vai o ponto importante:</p>
<blockquote>
<p>Object Calisthenics não deve ser aplicado como dogma.
Ele deve ser usado como lente.</p>
</blockquote>
<p>Vamos adaptar as 9 regras para a realidade do Go.</p>
<h2 id="1-apenas-um-nível-de-indentação-por-função">1. Apenas um nível de indentação por função</h2>
<p>Se sua função parece uma pirâmide, ela já está fazendo coisa demais.</p>
<p>Código em flecha:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-golang" data-lang="golang"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">func</span> <span style="color:#50fa7b">Process</span>(order Order) <span style="color:#8be9fd">error</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#ff79c6">if</span> order.<span style="color:#50fa7b">IsValid</span>() {
</span></span><span style="display:flex;"><span>		<span style="color:#ff79c6">if</span> order.<span style="color:#50fa7b">HasStock</span>() {
</span></span><span style="display:flex;"><span>			<span style="color:#ff79c6">if</span> err <span style="color:#ff79c6">:=</span> order.<span style="color:#50fa7b">Pay</span>(); err <span style="color:#ff79c6">!=</span> <span style="color:#ff79c6">nil</span> {
</span></span><span style="display:flex;"><span>				<span style="color:#ff79c6">return</span> err
</span></span><span style="display:flex;"><span>			}
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#ff79c6">return</span> <span style="color:#ff79c6">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Versão idiomática em Go:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-golang" data-lang="golang"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">func</span> <span style="color:#50fa7b">Process</span>(order Order) <span style="color:#8be9fd">error</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#ff79c6">if</span> !order.<span style="color:#50fa7b">IsValid</span>() {
</span></span><span style="display:flex;"><span>		<span style="color:#ff79c6">return</span> ErrInvalidOrder
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#ff79c6">if</span> !order.<span style="color:#50fa7b">HasStock</span>() {
</span></span><span style="display:flex;"><span>		<span style="color:#ff79c6">return</span> ErrNoStock
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#ff79c6">return</span> order.<span style="color:#50fa7b">Pay</span>()
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Go já incentiva isso naturalmente via guard clauses (Está PROIBIDO usar ELSE).</p>
<p>Essa regra é praticamente um reforço do estilo idiomático da linguagem.</p>
<h2 id="2-não-use-else">2. Não use else</h2>
<p>O else geralmente indica que você poderia ter retornado antes.</p>
<p>Em Go isso é ainda mais evidente:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-golang" data-lang="golang"><span style="display:flex;"><span><span style="color:#ff79c6">if</span> err <span style="color:#ff79c6">!=</span> <span style="color:#ff79c6">nil</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#ff79c6">return</span> err
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Esse padrão não é só estilo, ele reduz:</p>
<ul>
<li>Profundidade cognitiva</li>
<li>Complexidade ciclomática</li>
<li>Carga mental ao revisar código</li>
</ul>
<h2 id="3-encapsule-todos-os-primitivos-value-objects">3. Encapsule todos os primitivos (Value Objects)</h2>
<p>Aqui começa a ficar interessante em Go.</p>
<p>Problema comum:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-golang" data-lang="golang"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">func</span> <span style="color:#50fa7b">CreateUser</span>(id <span style="color:#8be9fd">int</span>, age <span style="color:#8be9fd">int</span>, email <span style="color:#8be9fd">string</span>)
</span></span></code></pre></div><p>Nada impede você de trocar id e age.</p>
<p>Agora:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-golang" data-lang="golang"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">type</span> UserID <span style="color:#8be9fd">int</span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">type</span> Age <span style="color:#8be9fd">int</span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">type</span> Email <span style="color:#8be9fd">string</span>
</span></span></code></pre></div><p>Ou melhor:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-golang" data-lang="golang"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">type</span> Email <span style="color:#8be9fd;font-style:italic">struct</span> {
</span></span><span style="display:flex;"><span>	value <span style="color:#8be9fd">string</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">func</span> <span style="color:#50fa7b">NewEmail</span>(v <span style="color:#8be9fd">string</span>) (Email, <span style="color:#8be9fd">error</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#ff79c6">if</span> !strings.<span style="color:#50fa7b">Contains</span>(v, <span style="color:#f1fa8c">&#34;@&#34;</span>) {
</span></span><span style="display:flex;"><span>		<span style="color:#ff79c6">return</span> Email{}, ErrInvalidEmail
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#ff79c6">return</span> Email{value: v}, <span style="color:#ff79c6">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Agora o erro é impossível de existir fora da criação.</p>
<p>Esse padrão é poderoso especialmente em sistemas de domínio rico, algo comum em produtos SaaS complexos.</p>
<h2 id="4-coleções-como-cidadãos-de-primeira-classe">4. Coleções como cidadãos de primeira classe</h2>
<p>Se você tem isso:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-golang" data-lang="golang"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">func</span> <span style="color:#50fa7b">CalculateTotal</span>(prices []<span style="color:#8be9fd">float64</span>) <span style="color:#8be9fd">float64</span>
</span></span></code></pre></div><p>Você espalha lógica por todo lugar.</p>
<p>Prefira:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-golang" data-lang="golang"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">type</span> Prices []<span style="color:#8be9fd">float64</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">func</span> (p Prices) <span style="color:#50fa7b">Total</span>() <span style="color:#8be9fd">float64</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#8be9fd;font-style:italic">var</span> total <span style="color:#8be9fd">float64</span>
</span></span><span style="display:flex;"><span>	<span style="color:#ff79c6">for</span> _, price <span style="color:#ff79c6">:=</span> <span style="color:#ff79c6">range</span> p {
</span></span><span style="display:flex;"><span>		total <span style="color:#ff79c6">+=</span> price
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#ff79c6">return</span> total
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Agora o comportamento mora com o dado.</p>
<p>Isso reduz:</p>
<ul>
<li>Vazamento de regras</li>
<li>Duplicação de loops</li>
<li>Inconsistência</li>
</ul>
<h2 id="5-um-ponto-por-linha-lei-de-demeter">5. Um ponto por linha (Lei de Demeter)</h2>
<p>Evite isso:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-golang" data-lang="golang"><span style="display:flex;"><span>order.Customer.Address.City.Name
</span></span></code></pre></div><p>Isso cria acoplamento estrutural profundo.</p>
<p>Prefira:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-golang" data-lang="golang"><span style="display:flex;"><span>order.<span style="color:#50fa7b">CityName</span>()
</span></span></code></pre></div><p>Se amanhã, a estrutura de City mudar, só um lugar precisa ser alterado.</p>
<h2 id="6-não-abrevie-nomes">6. Não abrevie nomes</h2>
<p>Go aceita variáveis curtas.
Mas isso não significa que tudo deve ser abreviado.</p>
<p>Existe diferença entre:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-golang" data-lang="golang"><span style="display:flex;"><span>i, r, w
</span></span></code></pre></div><p>E:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-golang" data-lang="golang"><span style="display:flex;"><span>usrSvcProcMgr
</span></span></code></pre></div><p>Se o nome precisa ser abreviado para caber, talvez a responsabilidade esteja grande demais.</p>
<h2 id="7-entidades-pequenas">7. Entidades pequenas</h2>
<p>Structs grandes quase sempre indicam:</p>
<ul>
<li>Baixa coesão</li>
<li>Muitas razões para mudar</li>
<li>Violação de SRP (<a href="https://rifeli.dev/blog/2017-03-25-principios-solid-srp-e-sopa-de-letrinhas/">tenho um artigo antigo sobre o assunto</a>)</li>
</ul>
<p>Se você tem:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-golang" data-lang="golang"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">type</span> UserService <span style="color:#8be9fd;font-style:italic">struct</span> {
</span></span><span style="display:flex;"><span>	repo Repository
</span></span><span style="display:flex;"><span>	cache Cache
</span></span><span style="display:flex;"><span>	mailer Mailer
</span></span><span style="display:flex;"><span>	logger Logger
</span></span><span style="display:flex;"><span>	metrics Metrics
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Talvez você tenha:</p>
<ul>
<li>Um Service Orquestrador</li>
<li>Um Service de Notificação</li>
<li>Um Service de Persistência</li>
</ul>
<blockquote>
<p>Composição &gt; concentração.</p>
</blockquote>
<h2 id="8-sem-getters-e-setters">8. Sem Getters e Setters</h2>
<p>Go já não força encapsulamento clássico.</p>
<p>Mas o problema real não é o método.
É o modelo anêmico.</p>
<p>Em vez de:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-golang" data-lang="golang"><span style="display:flex;"><span><span style="color:#ff79c6">if</span> order.<span style="color:#50fa7b">Status</span>() <span style="color:#ff79c6">==</span> Pending {
</span></span><span style="display:flex;"><span>	order.<span style="color:#50fa7b">SetStatus</span>(Paid)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Prefira:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-golang" data-lang="golang"><span style="display:flex;"><span>order.<span style="color:#50fa7b">Pay</span>()
</span></span></code></pre></div><p>Isso mantém regras de negócio dentro da entidade.</p>
<h2 id="9-no-máximo-dois-campos-por-struct">9. No máximo dois campos por struct</h2>
<p>Essa é a mais polêmica. Ela força alta coesão.</p>
<p>Mas em Go isso precisa ser interpretado com cuidado.</p>
<p>DTOs de transporte? Podem ter muitos campos.
Entidades de domínio? Devem ter poucos e bem relacionados.
Essa regra não é literal, é um detector de alerta.</p>
<h4 id="onde-object-calisthenics-não-funciona-bem-em-go">Onde Object Calisthenics NÃO funciona bem em Go:</h4>
<ul>
<li>Código de infraestrutura</li>
<li>Adaptadores HTTP</li>
<li>Serialização</li>
<li>Mappers</li>
<li>DTOs de banco</li>
</ul>
<p>Aplicar rigor extremo ali gera abstração artificial.</p>
<p>Object Calisthenics brilha em:</p>
<ul>
<li>Domínio rico</li>
<li>Lógica de negócio</li>
<li>Regras complexas</li>
<li>Sistemas que vivem muitos anos</li>
</ul>
<h4 id="o-verdadeiro-valor">O Verdadeiro Valor</h4>
<p>Object Calisthenics não é sobre OO.</p>
<p>É sobre:</p>
<ul>
<li>Coesão</li>
<li>Encapsulamento</li>
<li>Redução de acoplamento</li>
<li>Fluxos simples</li>
<li>Modelagem intencional</li>
</ul>
<p>Go não impõe arquitetura.</p>
<p>Ele expõe seu design.</p>
<p>E isso é perigoso.</p>
<p>Porque você pode escrever código simples…
ou simplesmente simplista.</p>
<h2 id="conclusão">Conclusão</h2>
<p>Aplicar as 9 regras ao mesmo tempo em produção pode ser exagero.</p>
<p>Mas praticá-las:</p>
<ul>
<li>Em refatorações</li>
<li>Em projetos paralelos</li>
<li>Em código crítico</li>
</ul>
<p>Treina seu cérebro para enxergar problemas antes deles virarem dívida técnica.</p>
<p>Design em Go não é sobre herança.
Não é sobre frameworks.
Não é sobre patterns da moda.</p>
<p>É sobre disciplina.
E disciplina não é imposta pela linguagem.
É escolhida pelo desenvolvedor.</p>
<p>Na <a href="https://harmo.me">Harmo</a>, usamos Object Calisthenics como disciplina de modelagem para manter nosso domínio coeso, explícito e sustentável ao longo do tempo.</p>
]]></content:encoded><category>golang</category><category>cleancode</category><category>refactoring</category><category>softwaredesign</category><category>bestpractices</category><category>objectcalisthenics</category><category>programming</category></item><item><title>Go é Orientada a Objetos? A resposta pragmática para uma dúvida comum</title><link>https://rifeli.dev/blog/go-e-orientada-a-objetos-paradigmas/</link><pubDate>Sat, 21 Feb 2026 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/go-e-orientada-a-objetos-paradigmas/</guid><description>Go é orientada a objetos? Descubra como a linguagem subverte os pilares clássicos ao trocar classes e herança por uma composição poderosa e interfaces implícitas. Entenda o pragmatismo por trás do design do Go e como ele se diferencia de linguagens como Java e C#.</description><content:encoded><![CDATA[<img id="image-custom" src="https://camo.githubusercontent.com/c53ca67708c63613f769ebd7fd9b5f030a308845b483d7f9189e60a625d0f857/68747470733a2f2f676f6c616e672e6f72672f646f632f676f706865722f6669766579656172732e6a7067" alt="cloud-native" />
<p id="image-legend">
  <a href="https://github.com/golang/go" target="_blank">golang/go</a>
</p>
<h2 id="introdução">Introdução</h2>
<p>Se você perguntar para três desenvolvedores se Go é uma linguagem Orientada a Objetos (OO), você provavelmente receberá quatro respostas diferentes. Uns dirão que é procedural &ldquo;com esteroides&rdquo;, outros que é funcional por causa das funções de primeira classe, e alguns defenderão que é OO purista.</p>
<p>Sempre gosto de dizer que a melhor linguagem é que resolverá o seu problema da melhor forma.</p>
<p>A verdade? Go é o que você precisa que ela seja, mas ela odeia burocracia (isso que me fez amar desde cedo).</p>
<p>Mas a realidade é mais sofisticada. Segundo o FAQ oficial do Go: &ldquo;Sim e não&rdquo;. Go é OO, mas não da forma que você aprendeu na faculdade. Vamos entender os pilares da OO sob a ótica de um Gopher.</p>
<h2 id="o-que-go-não-tem-e-por-que-isso-confunde">O que Go NÃO tem (e por que isso confunde)</h2>
<p>Para quem vem do Java ou C#, a falta de certos pilares pode parecer estranha:</p>
<ul>
<li><strong>Não existem classes:</strong> Temos structs;</li>
<li><strong>Não existe herança:</strong> Você não pode fazer uma struct <code>Dog extends Animal</code>;</li>
<li><strong>Não existe polimorfismo de subtipo:</strong> Esqueça as hierarquias complexas.</li>
</ul>
<h2 id="o-que-go-tem-a-alma-da-oo">O que Go TEM (A alma da OO)</h2>
<p>Se definirmos OO como &ldquo;objetos que encapsulam estado e expõem comportamento&rdquo;, Go é absolutamente OO:</p>
<ul>
<li><strong>Encapsulamento:</strong> Usamos maiúsculas e minúsculas para exportar (ou não) campos e funções;</li>
<li><strong>Métodos:</strong> Você pode pendurar métodos em qualquer tipo, não só em structs;</li>
<li><strong>Interfaces (O superpoder):</strong> Em Go, as interfaces são satisfeitas implicitamente. Se algo &ldquo;caminha como um pato&rdquo;, ele é um pato. Não precisa de implements.</li>
</ul>
<h5 id="o-veredito-composição-sobre-herança">O veredito: Composição sobre Herança</h5>
<p>Tenho um artigo antigo onde explico a diferença entre <a href="https://rifeli.dev/blog/2016-08-19-heranca-ou-composicao-qual-utilizar/">Herança e Composição</a>.</p>
<h2 id="sobre-o-veredito-go-é-orientada-a-objetos">Sobre o Veredito: Go é Orientada a Objetos?</h2>
<p>Se você define OO como hierarquias de classes e herança de tipos, então não.
Mas, se você define OO como um paradigma que permite:</p>
<ul>
<li>Encapsular estado;</li>
<li>Definir comportamento (métodos) para tipos;</li>
<li>Usar polimorfismo (interfaces).</li>
</ul>
<p>Então Go é uma das linguagens OO mais pragmáticas e eficientes que existem. Ela remove a &ldquo;cerimônia&rdquo; e foca na composição, o que evita o famoso problema da &ldquo;fronteira frágil&rdquo; em sistemas grandes.</p>
<p>Go força você a usar composição. Em vez de dizer que um Gerente é um Funcionario, em Go dizemos que um Gerente contém um Funcionario. É uma mudança de mindset que gera códigos mais flexíveis e fáceis de testar.</p>
<h2 id="exemplos">Exemplos</h2>
<h4 id="1-encapsulamento-o-poder-das-iniciais">1. Encapsulamento: O poder das iniciais</h4>
<p>Em linguagens tradicionais, usamos <strong>public</strong>, <strong>private</strong> e <strong>protected</strong>. Em Go, o encapsulamento é resolvido no nível do package através da capitalização.</p>
<ul>
<li>Inicia com Maiúscula: Exportado (Público);</li>
<li>Inicia com Minúscula: Não exportado (Privado ao package).</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-golang" data-lang="golang"><span style="display:flex;"><span><span style="color:#ff79c6">package</span> account
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">type</span> CheckingAccount <span style="color:#8be9fd;font-style:italic">struct</span> {
</span></span><span style="display:flex;"><span>  Owner  <span style="color:#8be9fd">string</span>  <span style="color:#6272a4">// Público</span>
</span></span><span style="display:flex;"><span>  balance <span style="color:#8be9fd">float64</span> <span style="color:#6272a4">// Privado: ninguém fora do package &#39;account&#39; altera o saldo direto</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4">// NewCheckingAccount é o nosso &#34;Construtor&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">func</span> <span style="color:#50fa7b">NewCheckingAccount</span>(owner <span style="color:#8be9fd">string</span>) <span style="color:#ff79c6">*</span>CheckingAccount {
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">return</span> <span style="color:#ff79c6">&amp;</span>CheckingAccount{Owner: owner, balance: <span style="color:#bd93f9">0</span>}
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4">// Deposit é um método (comportamento)</span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">func</span> (c <span style="color:#ff79c6">*</span>CheckingAccount) <span style="color:#50fa7b">Deposit</span>(amount <span style="color:#8be9fd">float64</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">if</span> amount &gt; <span style="color:#bd93f9">0</span> {
</span></span><span style="display:flex;"><span>    c.balance <span style="color:#ff79c6">+=</span> amount
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h4 id="2-métodos-em-qualquer-lugar">2. Métodos em qualquer lugar</h4>
<p>Diferente do Java, onde métodos pertencem a classes, em Go você pode adicionar comportamento a qualquer tipo definido no seu package. Isso é OO puro: associar dado ao comportamento.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-golang" data-lang="golang"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">type</span> Celsius <span style="color:#8be9fd">float64</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4">// Adicionando comportamento a um tipo primitivo (float64)</span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">func</span> (c Celsius) <span style="color:#50fa7b">IsFreezing</span>() <span style="color:#8be9fd">bool</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">return</span> c <span style="color:#ff79c6">&lt;=</span> <span style="color:#bd93f9">0</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">func</span> <span style="color:#50fa7b">main</span>() {
</span></span><span style="display:flex;"><span>  temp <span style="color:#ff79c6">:=</span> <span style="color:#50fa7b">Celsius</span>(<span style="color:#bd93f9">25.5</span>)
</span></span><span style="display:flex;"><span>  fmt.<span style="color:#50fa7b">Println</span>(temp.<span style="color:#50fa7b">IsFreezing</span>()) <span style="color:#6272a4">// false</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h4 id="3-adeus-herança-olá-composição">3. Adeus Herança, Olá Composição</h4>
<p>Este é o ponto onde muitos desenvolvedores &ldquo;travam&rdquo;. Go não permite que uma struct herde campos e métodos de outra. Em vez disso, usamos Struct Embedding (composição).</p>
<p>Em vez de dizer que um Gerente é um Funcionario, dizemos que um Gerente contém um Funcionario.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-golang" data-lang="golang"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">type</span> User <span style="color:#8be9fd;font-style:italic">struct</span> {
</span></span><span style="display:flex;"><span>  Name  <span style="color:#8be9fd">string</span>
</span></span><span style="display:flex;"><span>  Email <span style="color:#8be9fd">string</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">func</span> (u User) <span style="color:#50fa7b">Label</span>() <span style="color:#8be9fd">string</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">return</span> fmt.<span style="color:#50fa7b">Sprintf</span>(<span style="color:#f1fa8c">&#34;%s (%s)&#34;</span>, u.Name, u.Email)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">type</span> Admin <span style="color:#8be9fd;font-style:italic">struct</span> {
</span></span><span style="display:flex;"><span>  User  <span style="color:#6272a4">// Embedding: Admin &#34;ganha&#34; os campos e métodos de User</span>
</span></span><span style="display:flex;"><span>  Level <span style="color:#8be9fd">int</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">func</span> <span style="color:#50fa7b">main</span>() {
</span></span><span style="display:flex;"><span>  a <span style="color:#ff79c6">:=</span> Admin{
</span></span><span style="display:flex;"><span>      User:  User{Name: <span style="color:#f1fa8c">&#34;Alice&#34;</span>, Email: <span style="color:#f1fa8c">&#34;alice@dev.com&#34;</span>},
</span></span><span style="display:flex;"><span>      Level: <span style="color:#bd93f9">1</span>,
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  
</span></span><span style="display:flex;"><span>  <span style="color:#6272a4">// Podemos acessar Label() direto em &#39;a&#39; como se fosse dele</span>
</span></span><span style="display:flex;"><span>  fmt.<span style="color:#50fa7b">Println</span>(a.<span style="color:#50fa7b">Label</span>()) 
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h4 id="4interfaces-o-polimorfismo-implícito">4.Interfaces: O Polimorfismo Implícito</h4>
<p>O maior trunfo do Go é a interface. Em C#, você precisa declarar <code>class Pato: IPato</code>. Em Go, se a sua struct tem os métodos que uma interface pede, ela automaticamente satisfaz a interface.</p>
<p>Isso é chamado de Structural Typing (ou Duck Typing estático).</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-golang" data-lang="golang"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">type</span> Speaker <span style="color:#8be9fd;font-style:italic">interface</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#50fa7b">Speak</span>() <span style="color:#8be9fd">string</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">type</span> Dog <span style="color:#8be9fd;font-style:italic">struct</span>{}
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">type</span> Robot <span style="color:#8be9fd;font-style:italic">struct</span>{}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">func</span> (d Dog) <span style="color:#50fa7b">Speak</span>() <span style="color:#8be9fd">string</span> { 
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">return</span> <span style="color:#f1fa8c">&#34;Au Au!&#34;</span> 
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">func</span> (r Robot) <span style="color:#50fa7b">Speak</span>() <span style="color:#8be9fd">string</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">return</span> <span style="color:#f1fa8c">&#34;Beep Boop!&#34;</span> 
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4">// Esta função não sabe (nem se importa) o que é um Dog ou Robot</span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">func</span> <span style="color:#50fa7b">MakeItSpeak</span>(s Speaker) {
</span></span><span style="display:flex;"><span>  fmt.<span style="color:#50fa7b">Println</span>(s.<span style="color:#50fa7b">Speak</span>())
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h2 id="o-que-vem-a-seguir">O que vem a seguir?</h2>
<p>Agora que entendemos que Go favorece a composição e o desacoplamento, como aplicamos regras rígidas de design para manter esse código limpo? No próximo artigo, vamos explorar o <strong>Object Calisthenics</strong> aplicado ao Go.</p>
<p>Mas, você já pode dar uma lida sobre, neste artigo do mestre Elton Minetto sobre o assunto: <a href="https://eltonminetto.dev/2016/06/24/como-melhorar-seus-codigos-usando-object-calisthenics/">Como melhorar seus códigos usando Object Calisthenics
</a></p>
<h2 id="conclusão">Conclusão</h2>
<p>Go é uma linguagem multiparadigma que abraça a simplicidade. Ela pega o melhor da OO (interfaces e encapsulamento) e joga fora o pior (hierarquias de herança profundas).</p>
]]></content:encoded><category>golang</category><category>programacao</category><category>oop</category><category>softwareengineering</category><category>backend</category><category>desenvolvimento</category><category>tecnologia</category></item><item><title>Por que o tooling nativo do Go aumenta sua produtividade, e como fazer profiling como um profissional</title><link>https://rifeli.dev/blog/tooling-nativo-go-produtividade-e-profiling-pprof/</link><pubDate>Tue, 18 Nov 2025 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/tooling-nativo-go-produtividade-e-profiling-pprof/</guid><description>Entenda como o ecossistema nativo do Go: go fmt, vet, test, mod e pprof, multiplica sua produtividade e aprenda a fazer profiling de CPU, memória e goroutines sem bibliotecas externas.</description><content:encoded><![CDATA[<img id="image-custom" src="/images/posts/4550be14-d782-4e57-ab46-d5708eec30a8.png" alt="cloud-native" />
<p id="image-legend"></p>
<h2 id="introdução">Introdução</h2>
<p>A maioria das linguagens modernas prometem produtividade, mas poucas realmente entregam isso na prática sem depender de plugins, frameworks adicionais ou configurações complexas. Com Go, a história é diferente. Programo em Go desde a versão 1.7, e lá já tínhamos alguns recursos nativos, inclusive o próprio <code>pprof</code>. E atualmente, logo após instalar a linguagem, você descobre que não ganhou apenas um compilador, mas também um ecossistema completo de ferramentas, projetado para reduzir atrito, eliminar decisões desnecessárias e permitir que times construam software robusto com muito menos esforço.</p>
<p>Foi justamente isso que me fez perceber por que Go se tornou a base de projetos gigantes como Kubernetes, Docker, Etcd, Terraform e tantos outros sistemas críticos: a linguagem é simples, mas o tooling nativo é extraordinariamente poderoso.</p>
<p>E entre todas essas ferramentas, uma mega importante (que comentei no inicio), existe e poucos desenvolvedores exploram a fundo, mas que deveria ser usada diariamente em aplicações que buscam alta performance: o pprof, o profiler nativo capaz de revelar CPU hotspots, consumo real de memória, leaks, disputas entre goroutines, blocagens, mutexes e tudo que realmente importa em sistemas concorrentes.</p>
<p>Este artigo explora justamente isso:
como o tooling nativo do Go multiplica produtividade desde o primeiro dia, e como o profiling com <code>pprof</code> te permite evoluir sua aplicação com decisões baseadas em evidência, não em achismo. Além, de passar por outras toolchains nativas que ajudam na produtividade.</p>
<h2 id="por-que-o-tooling-nativo-do-go">Por que o tooling nativo do Go?</h2>
<p>O Tooling nativo do Go é um divisor de águas para a produtividade, mas como usar o poder do profiling para ir além?</p>
<p>Quando falamos sobre Go, é comum destacar sua simplicidade, velocidade de compilação e facilidade de manutenção. Mas, depois de anos trabalhando com a linguagem, percebi que o verdadeiro diferencial não é apenas a sintaxe minimalista: é o ecossistema de ferramentas nativas que vêm embutidas no próprio Go.</p>
<p>Enquanto outras linguagens dependem fortemente de bibliotecas externas para tarefas essenciais, como: formatação, análise estática, testes, gestão de dependências, segurança. Go entrega tudo isso sem você instalar absolutamente nada além da linguagem.</p>
<p>E isso muda o jogo.</p>
<h2 id="profiling-nativo">profiling nativo</h2>
<p>Você pode ter código limpo, testes robustos e dependências organizadas, mas sem medir performance, qualquer otimização é chute e mero achismo. É aí que entra o santo graal do ecossistema Go:</p>
<p><code>pprof</code>: profiling nativo de CPU, memória, goroutines e mais</p>
<p>O Go inclui um profiler completo, extremamente eficiente e simples de usar. Nada de instalar libs ou plugins. Basta importar um pacote e rodar seu app.</p>
<p>Mas, vamos por partes.</p>
<h3 id="o-que-você-pode-medir-com-o-pprof">O que você pode medir com o pprof?</h3>
<p>Com o profiler nativo, você consegue extrair insights críticos:</p>
<ul>
<li><code>CPU</code>: quais funções consomem mais processamento;</li>
<li><code>Memória</code>: onde está o maior volume de alocações;</li>
<li><code>Goroutines</code>: leaks e explosão de concorrência;</li>
<li><code>Blocagem</code>: contenção de mutexes;</li>
<li><code>Heap / stack</code>: entender crescimento ao longo do tempo;</li>
<li><code>Latência</code>: registrar delays inesperados no fluxo.</li>
</ul>
<p>Tudo isso com overhead extremamente baixo, adequado inclusive para produção.</p>
<h2 id="como-utilizar-o-profiling">Como utilizar o profiling</h2>
<p>Para extrair o máximo de dados da sua aplicação é muito simples.</p>
<ol>
<li>Habilitar endpoint de profiling no servidor</li>
</ol>
<p>O pprof já vem com um servidor HTTP pronto. Fiz um código com diversos problemas para validarmos.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-golang" data-lang="golang"><span style="display:flex;"><span><span style="color:#ff79c6">package</span> main
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">import</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#f1fa8c">&#34;log&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#f1fa8c">&#34;math/rand&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#f1fa8c">&#34;net/http&#34;</span>
</span></span><span style="display:flex;"><span>	_ <span style="color:#f1fa8c">&#34;net/http/pprof&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#f1fa8c">&#34;time&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">func</span> <span style="color:#50fa7b">workCPU</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#ff79c6">for</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#6272a4">// Simula um cálculo pesado</span>
</span></span><span style="display:flex;"><span>		x <span style="color:#ff79c6">:=</span> <span style="color:#bd93f9">0</span>
</span></span><span style="display:flex;"><span>		<span style="color:#ff79c6">for</span> i <span style="color:#ff79c6">:=</span> <span style="color:#bd93f9">0</span>; i &lt; <span style="color:#bd93f9">5_000_000</span>; i<span style="color:#ff79c6">++</span> {
</span></span><span style="display:flex;"><span>			x <span style="color:#ff79c6">+=</span> rand.<span style="color:#50fa7b">Intn</span>(<span style="color:#bd93f9">10</span>)
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		_ = x
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">func</span> <span style="color:#50fa7b">workMemory</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#8be9fd;font-style:italic">var</span> store [][]<span style="color:#8be9fd">byte</span>
</span></span><span style="display:flex;"><span>	<span style="color:#ff79c6">for</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#6272a4">// Aloca memória aleatória \o/</span>
</span></span><span style="display:flex;"><span>		b <span style="color:#ff79c6">:=</span> <span style="color:#8be9fd;font-style:italic">make</span>([]<span style="color:#8be9fd">byte</span>, rand.<span style="color:#50fa7b">Intn</span>(<span style="color:#bd93f9">5_000_00</span>))
</span></span><span style="display:flex;"><span>		store = <span style="color:#8be9fd;font-style:italic">append</span>(store, b)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#6272a4">// Limpando de tempo em tepo, para mudar o padrão de alocação</span>
</span></span><span style="display:flex;"><span>		<span style="color:#ff79c6">if</span> <span style="color:#8be9fd;font-style:italic">len</span>(store) &gt; <span style="color:#bd93f9">50</span> {
</span></span><span style="display:flex;"><span>			store = store[:<span style="color:#bd93f9">0</span>]
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		time.<span style="color:#50fa7b">Sleep</span>(<span style="color:#bd93f9">50</span> <span style="color:#ff79c6">*</span> time.Millisecond)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">func</span> <span style="color:#50fa7b">workGoroutines</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#ff79c6">for</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#ff79c6">go</span> <span style="color:#8be9fd;font-style:italic">func</span>() {
</span></span><span style="display:flex;"><span>			time.<span style="color:#50fa7b">Sleep</span>(<span style="color:#bd93f9">100</span> <span style="color:#ff79c6">*</span> time.Millisecond)
</span></span><span style="display:flex;"><span>		}()
</span></span><span style="display:flex;"><span>		time.<span style="color:#50fa7b">Sleep</span>(<span style="color:#bd93f9">10</span> <span style="color:#ff79c6">*</span> time.Millisecond)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">func</span> <span style="color:#50fa7b">main</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#6272a4">// Inicia o servidor pprof</span>
</span></span><span style="display:flex;"><span>	<span style="color:#ff79c6">go</span> <span style="color:#8be9fd;font-style:italic">func</span>() {
</span></span><span style="display:flex;"><span>		log.<span style="color:#50fa7b">Println</span>(<span style="color:#f1fa8c">&#34;pprof ativo em :6060&#34;</span>)
</span></span><span style="display:flex;"><span>		log.<span style="color:#50fa7b">Println</span>(<span style="color:#f1fa8c">&#34;Acesse: http://localhost:6060/debug/pprof/&#34;</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#ff79c6">if</span> err <span style="color:#ff79c6">:=</span> http.<span style="color:#50fa7b">ListenAndServe</span>(<span style="color:#f1fa8c">&#34;localhost:6060&#34;</span>, <span style="color:#ff79c6">nil</span>); err <span style="color:#ff79c6">!=</span> <span style="color:#ff79c6">nil</span> {
</span></span><span style="display:flex;"><span>			log.<span style="color:#50fa7b">Fatal</span>(err)
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	}()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#6272a4">// Carga para gerar perfis reais</span>
</span></span><span style="display:flex;"><span>	<span style="color:#ff79c6">go</span> <span style="color:#50fa7b">workCPU</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#ff79c6">go</span> <span style="color:#50fa7b">workMemory</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#ff79c6">go</span> <span style="color:#50fa7b">workGoroutines</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#6272a4">// Mantém a aplicação viva</span>
</span></span><span style="display:flex;"><span>	<span style="color:#ff79c6">select</span> {}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Após, só acessar: http://localhost:6060/debug/pprof/</p>
<img id="image-custom" src="/images/posts/proff.png" alt="cloud-native" />
<p id="image-legend">Aplicação pprof rodando em :6060</p>
<ol start="2">
<li>Coletar um perfil de CPU</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>go tool pprof http://localhost:6060/debug/pprof/profile
</span></span></code></pre></div><p>O Go captura 30s de execuções por padrão.
Depois abre um shell interativo onde você pode rodar:</p>
<ul>
<li><code>top</code></li>
<li><code>top -cum</code></li>
<li><code>web</code> (gera um gráfico FlameGraph automático!)</li>
</ul>
<p>Pra usar o web, você precisa instalar o <code>Graphviz</code>.</p>
<p>Resultado:</p>
<img id="image-custom" src="/images/posts/pprof-terminal.png" alt="cloud-native" />
<p id="image-legend">O shell interativo após o pprof ter finalizado</p>
<img id="image-custom" src="/images/posts/pprof-cpu-web.png" alt="cloud-native" />
<p id="image-legend">Versão WEB com o mapa da aplicação</p>
<p>Você pode fazer isso também:</p>
<h3 id="memória">Memória</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>go tool pprof http://localhost:6060/debug/pprof/heap
</span></span></code></pre></div><p>Ideal para:</p>
<ul>
<li>identificar alocações frequentes;</li>
<li>descobrir vazamentos;</li>
<li>otimizar estruturas de dados.</li>
</ul>
<h3 id="goroutines">Goroutines</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>curl http://localhost:6060/debug/pprof/goroutine?debug<span style="color:#ff79c6">=</span><span style="color:#bd93f9">2</span>
</span></span></code></pre></div><p>Ideal para:</p>
<ul>
<li>encontrar deadlocks</li>
<li>goroutines que nunca finalizam</li>
<li>fan-outs excessivos</li>
</ul>
<h2 id="exemplo-mais-prático-ainda">Exemplo mais prático ainda</h2>
<p>Imagine que sua função mais crítica está lenta:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-golang" data-lang="golang"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">func</span> <span style="color:#50fa7b">Process</span>() {
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">for</span> i <span style="color:#ff79c6">:=</span> <span style="color:#bd93f9">0</span>; i &lt; <span style="color:#bd93f9">10_000_000</span>; i<span style="color:#ff79c6">++</span> {
</span></span><span style="display:flex;"><span>    _ = fmt.<span style="color:#50fa7b">Sprintf</span>(<span style="color:#f1fa8c">&#34;%d&#34;</span>, i)
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Rodando o profiler:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>go tool pprof http://localhost:6060/debug/pprof/profile
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">(</span>pprof<span style="color:#ff79c6">)</span> top
</span></span></code></pre></div><p>Veremos algo como:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>80% runtime.slicebytetostring
</span></span><span style="display:flex;"><span>15% fmt.<span style="color:#ff79c6">(</span>*pp<span style="color:#ff79c6">)</span>.doPrint
</span></span><span style="display:flex;"><span>5% sua função
</span></span></code></pre></div><p>Diagnóstico imediato:</p>
<blockquote>
<p>fmt.Sprintf é absurdamente lento para esse caso.</p>
</blockquote>
<p>Solução:</p>
<ul>
<li>usar <code>strconv.Itoa</code> (100x mais rápido);</li>
<li>evitar alocações com buffer pré-alocado.</li>
</ul>
<p>Esse tipo de melhoria só aparece com profiler, dificilmente com &ldquo;feeling&rdquo;.</p>
<h2 id="por-que-o-profiler-nativo-é-tão-poderoso">Por que o profiler nativo é tão poderoso?</h2>
<p>Porque ele é integrado ao runtime. Ele entende:</p>
<ul>
<li>scheduler do Go</li>
<li>garbage collector</li>
<li>blocagem de goroutines</li>
<li>time spent inside syscalls</li>
<li>concorrência e contenção de locks</li>
</ul>
<p>Ferramentas externas jamais alcançam esse nível de precisão.</p>
<h2 id="toolchains-nativas-que-também-auxiliam">Toolchains nativas que também auxiliam</h2>
<p>🧰 O toolchain nativo que multiplica sua produtividade</p>
<ol>
<li><code>go fmt / gofmt</code>: Formatação automática</li>
</ol>
<ul>
<li>Código sempre consistente;</li>
<li>Zero debates sobre estilo no time;</li>
<li>Formatação idempotente e global.</li>
</ul>
<ol start="2">
<li><code>go vet</code>: Análise estática poderosa</li>
</ol>
<ul>
<li>Detecta bugs sutis antes mesmo da compilação;</li>
<li>Erros de Printf, escapes de variáveis, loops suspeitos;</li>
<li>Evita classes inteiras de falhas silenciosas.</li>
</ul>
<ol start="3">
<li><code>go test</code>: Testes, benchmarks e fuzzing</li>
</ol>
<ul>
<li>Framework nativo;</li>
<li>Sem dependências externas;</li>
<li>Comando único para testes, -bench para benchmarks, -fuzz para fuzz testing.</li>
</ul>
<ol start="4">
<li><code>go mod</code>: Dependências previsíveis</li>
</ol>
<ul>
<li>Cache global;</li>
<li>Builds reproduzíveis;</li>
<li>Versionamento semântico respeitado automaticamente.</li>
</ul>
<p>Quando comecei em Golang, na v1.7 era punk rsrs!</p>
<ol start="5">
<li><code>govulncheck</code>: Segurança integrada</li>
</ol>
<ul>
<li>Scanner oficial de vulnerabilidades;</li>
<li>Analisa código e binários;</li>
<li>Foco em vulnerabilidades reais, não ruído.</li>
</ul>
<h2 id="conclusão">Conclusão</h2>
<p>Go te entrega uma caixa de ferramentas completa, use tudo.</p>
<p>O Go é rápido, simples e direto. Mas o que realmente o coloca em outro patamar é seu tooling nativo:</p>
<ul>
<li><code>fmt</code> padroniza;</li>
<li><code>vet</code> previne;</li>
<li><code>test</code> garante;</li>
<li><code>mod</code> organiza;</li>
<li><code>govulncheck</code> protege;</li>
<li><code>pprof</code> evolui seu sistema de verdade.</li>
</ul>
<p>Se você quer extrair o máximo da linguagem, aprender profiling é obrigatório.
Com dois comandos você já está medindo performance de forma profissional:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>go tool pprof -http<span style="color:#ff79c6">=</span>:8081 cpu.prof
</span></span><span style="display:flex;"><span>go tool pprof -http<span style="color:#ff79c6">=</span>:8082 mem.prof
</span></span></code></pre></div><p>E você começa a enxergar sua aplicação como ela realmente é e se comporta, não como você imagina.</p>
<p>Se você já utiliza o tooling nativo do Go no dia a dia, principalmente profiling com pprof, compartilhe sua experiência comigo.
Quais ferramentas mais aceleram sua produtividade? Que desafios você já resolveu com profiling?
Seu ponto de vista enriquece a discussão e ajuda outros desenvolvedores a evoluírem também.</p>
<p>Deixe seu comentário, compartilhe o artigo e vamos continuar construindo software de alta performance juntos. 🚀</p>
]]></content:encoded><category>go</category><category>performance</category><category>engenharia</category><category>backend</category></item><item><title>Lições aprendidas rodando uma plataforma cloud-native na AWS</title><link>https://rifeli.dev/blog/licoes-aprendidas-rodando-plataforma-cloudnative-na-aws/</link><pubDate>Fri, 14 Nov 2025 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/licoes-aprendidas-rodando-plataforma-cloudnative-na-aws/</guid><description>Principais aprendizados técnicos, operacionais e culturais ao escalar uma plataforma cloud-native distribuída na AWS usando EKS, Aurora, SQS, Lambda, Firehose e práticas de observabilidade.</description><content:encoded><![CDATA[<img id="image-custom" src="https://media.licdn.com/dms/image/v2/D4E12AQG24U3aecIA1A/article-cover_image-shrink_720_1280/article-cover_image-shrink_720_1280/0/1684491687372?e=2147483647&v=beta&t=QUlDIoctKT1ERNjUlU-rUk-tFEEDr7kW7BH074DO3Xg" alt="cloud-native" />
<p id="image-legend"></p>
<h2 id="introdução">Introdução</h2>
<p>Nos últimos anos, a forma como construímos e operamos sistemas evoluiu radicalmente (sou do tempo que subiamos arquivos via sFTP). A migração para a nuvem deixou de ser apenas um movimento tecnológico e passou a ser um movimento cultural, que redefine não só onde rodamos software, mas principalmente como pensamos, desenvolvemos, implantamos e mantemos aplicações em produção.</p>
<p>Antes de entrar nas lições práticas sobre operar uma plataforma cloud-native na AWS, é fundamental alinhar dois conceitos que frequentemente são confundidos, mas que definem todo o restante da arquitetura moderna: Cloud e Cloud-Native.</p>
<h2 id="o-que-é-cloud">O que é Cloud?</h2>
<p>Cloud é um modelo de computação com base no acesso sob demanda a recursos de infraestrutura, armazenamento, redes, dados e serviços gerenciados através da internet.</p>
<p>A nuvem permite:</p>
<ul>
<li>provisionar recursos em minutos, não semanas;</li>
<li>escalar horizontalmente;</li>
<li>automatizar ambientes inteiros;</li>
<li>reduzir custos ao pagar só pelo uso;</li>
<li>acessar serviços avançados sem comprar hardware;</li>
<li>criar ambientes efêmeros para testes e validação.</li>
</ul>
<p>Mais importante que isso: a nuvem muda o ritmo da engenharia, possibilitando uma velocidade que seria impossível em ambientes tradicionais.</p>
<h2 id="o-que-é-cloud-native">O que é Cloud-Native?</h2>
<p>Cloud-native não significa simplesmente &ldquo;estar na nuvem&rdquo;.
Significa projetar e operar sistemas que exploram a nuvem ao máximo.</p>
<p>Um sistema cloud-native nasce com:</p>
<ul>
<li>elasticidade como padrão,</li>
<li>resiliência embutida,</li>
<li>automação de ponta a ponta,</li>
<li>forte observabilidade,</li>
<li>implantação contínua,</li>
<li>componentes desacoplados,</li>
<li>infraestrutura declarativa,</li>
<li>e claro, alta tolerância a falhas.</li>
</ul>
<p>Em outras palavras:</p>
<p>Cloud-native é a combinação de arquitetura, cultura e automação para que sistemas sobrevivam e evoluam em ambientes dinâmicos.</p>
<h2 id="lições-aprendidas">Lições Aprendidas</h2>
<p>Construir e operar uma plataforma verdadeiramente <strong>cloud-native</strong> vai muito além de rodar workloads em containers. Envolve pessoas, processos, arquitetura, governança, automação e uma disciplina contínua de observabilidade.</p>
<p>Nos últimos anos rodando uma stack distribuída na AWS, com EKS, Aurora PostgreSQL, Firehose, Lambda, SQS, OpenSearch, Athena e pipelines de GenAI, aprendemos que o maior desafio não é adotar a nuvem, e sim <strong>operacionalizá-la com confiabilidade e custo-eficiência</strong>.</p>
<p>Agora, trago as principais lições aprendidas em todo esse processo.</p>
<h3 id="1-cloud-native-não-é-kubernetes-é-um-mindset">1. Cloud-native não é Kubernetes: é um mindset</h3>
<p>Quando migramos nossos primeiros serviços para EKS, fizemos o clássico erro: tratamos Kubernetes como se fosse um datacenter com YAML.</p>
<p>Cloud-native é:</p>
<ul>
<li><strong>Elasticidade real</strong></li>
<li><strong>Efemeridade como padrão</strong></li>
<li><strong>Observabilidade first-class</strong></li>
<li><strong>Automação desde o início</strong></li>
</ul>
<p>A mudança mais impactante não foi a tecnologia, mas sim <strong>abandonar o &ldquo;servidorzão confiável&rdquo; e abraçar o caos controlado</strong>.</p>
<h3 id="2-observabilidade-é-o-que-transforma-caos-em-previsibilidade">2. Observabilidade é o que transforma caos em previsibilidade</h3>
<p>Os maiores problemas vieram de <strong>falta de visibilidade</strong>.</p>
<p>Lições essenciais:</p>
<ul>
<li>CloudWatch sozinho não resolve, centralize o máximo possível (aqui usamos OpenSearch)</li>
<li>Busque correlacionar as requisições via <code>trace_id</code> (microserviço A fala com B, etc)</li>
<li>Crie SLOs reais com alertas de burn rate</li>
<li>Sem P95/P99 você está cego</li>
</ul>
<p><strong>Sem observabilidade, Kubernetes vira aleatoriedade.</strong></p>
<p>PS.: Em outro artigo, falarei como nós fazemos.</p>
<h3 id="3-custos-se-gerenciam-no-código-não-no-financeiro">3. Custos se gerenciam no código, não no financeiro</h3>
<p>A AWS cobra por tudo que movimenta, armazena ou respira. Costumo dizer que AWS escala na mesma proporção que o limite do cartão de crédito.s</p>
<p>Aprendizados:</p>
<ul>
<li>O custo real do EKS são os nós, não o cluster</li>
<li>Aurora cobra caro em I/O (busca usar aurora cluster com I/O otimizado)</li>
<li>OpenSearch escala a fatura rápido demais</li>
<li>Firehose precisa de compressão</li>
</ul>
<p>Estratégias que funcionaram:</p>
<ul>
<li>Autoscaling agressivo</li>
<li>Instâncias Spot (cuidado com interrupções)</li>
<li>Revisão mensal FinOps</li>
</ul>
<p><strong>Custos são uma skill técnica.</strong></p>
<p>Busque alinhar o custo da infra com o business.</p>
<h3 id="4-multi-account-é-segurança-operacional">4. Multi-account é segurança operacional</h3>
<p>O modelo multi-account trouxe:</p>
<ul>
<li>Limitação de impactos</li>
<li>Ambientes isolados de verdade</li>
<li>Controle granular de IAM</li>
<li>Governança mais previsível</li>
</ul>
<p>Ambientes independentes tornam incidentes muito menos catastróficos.</p>
<h3 id="5-deploy-contínuo-só-é-bom-com-rollback-contínuo">5. Deploy contínuo só é bom com rollback contínuo</h3>
<p>CI/CD não é sobre deploy rápido, é sobre <strong>desfazer rápido</strong>.</p>
<p>Práticas essenciais:</p>
<ul>
<li>Blue/Green ou Canary (estamos melhorando)</li>
<li>Feature flags</li>
<li>Versionamento de configs</li>
<li>Health checks reais</li>
</ul>
<p>Um deploy não termina até existir rollback de código, infra e dados.</p>
<h3 id="6-o-banco-é-o-coração-mas-também-é-o-gargalo">6. O banco é o coração, mas também é o gargalo</h3>
<p>Aurora PostgreSQL é poderoso, mas impõe lições:</p>
<ul>
<li>Cuidado com I/O (pode sair muito caro)</li>
<li>Use BRIN em tabelas largas</li>
<li>Separe leituras e escritas</li>
<li>Use cache (Redis) para amenizar instância e I/O</li>
</ul>
<p>Quando o banco vira gargalo, quase sempre <strong>a culpa é sua</strong>.</p>
<h3 id="7-autoscaling-só-funciona-com-stateless-de-verdade">7. Autoscaling só funciona com stateless de verdade</h3>
<p>Problemas clássicos:</p>
<ul>
<li>Sticky sessions</li>
<li>Buffers locais</li>
<li>Operações não idempotentes</li>
</ul>
<p>Quando a plataforma virou 100% stateless, o HPA finalmente começou a funcionar como esperado.</p>
<h3 id="8-segurança-é-atributo-do-design">8. Segurança é atributo do design</h3>
<p>Funciona quando:</p>
<ul>
<li>IAM é minimizado</li>
<li>Roles são por serviço</li>
<li>VPC é segmentada</li>
<li>Imagens são imutáveis</li>
<li>Secrets são gerenciados</li>
</ul>
<p>Segurança não é time; <strong>é arquitetura.</strong></p>
<h3 id="9-testes-em-produção-não-são-tabus-são-obrigatórios">9. Testes em produção não são tabus, são obrigatórios</h3>
<p>Claro, sempre com responsabilidade:</p>
<ul>
<li>Chaos engineering leve</li>
<li>Canary releases</li>
<li>Traffic shadowing</li>
<li>Testes de carga periódicos</li>
</ul>
<p>Nada simula produção, então <strong>teste em produção com método</strong>.</p>
<h3 id="10-as-maiores-lições-são-humanas">10. As maiores lições são humanas</h3>
<p>Nada substitui:</p>
<ul>
<li>Disciplina de engenharia</li>
<li>Comunicação clara</li>
<li>Documentação viva</li>
<li>Postmortems sem culpados (mas com ações)</li>
<li>On-call saudável</li>
</ul>
<p>Ferramentas viram plataforma quando existe <strong>cultura</strong>.</p>
<h2 id="conclusão">Conclusão</h2>
<p>Rodar uma plataforma cloud-native na AWS é uma jornada contínua.</p>
<p>A tecnologia evolui, os desafios mudam, mas a essência permanece.</p>
<blockquote>
<p><strong>Cloud-native não é sobre tecnologia, mas sim sobre resiliência, automação e maturidade de engenharia.</strong></p>
</blockquote>
]]></content:encoded><category>cloud</category><category>aws</category><category>arquitetura</category><category>devops</category><category>engenharia</category></item><item><title>Como estruturamos nosso pipeline CI/CD para aplicações cloud-native no EKS</title><link>https://rifeli.dev/blog/como-fazemos-ci-cd-na-harmo/</link><pubDate>Sat, 01 Nov 2025 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/como-fazemos-ci-cd-na-harmo/</guid><description>Entenda como funciona nosso pipeline CI/CD usando GitHub, Copilot AI, CircleCI, AWS ECR, Jenkins e Kubernetes (EKS) para garantir deploys previsíveis, seguros e escaláveis.</description><content:encoded><![CDATA[<img id="image-custom" src="https://assets.primotech.com/wp-content/uploads/2022/07/CICD-Pipeline-Everything-You-Need-to-Know-1024x546.png" alt="cloud-native" />
<p id="image-legend"></p>
<h2 id="introdução">Introdução</h2>
<p>Construir uma plataforma cloud-native moderna não depende apenas de Kubernetes, observabilidade e arquitetura distribuída, mas sim de um <strong>pipeline de entrega contínua confiável</strong>, capaz de compilar, testar, versionar, empacotar e implantar aplicações de forma consistente.</p>
<p>Na Harmo, adotamos um fluxo que conecta:</p>
<ul>
<li><strong>GitHub</strong> (código + PRs)</li>
<li><strong>Copilot AI</strong> (code-review, refinamento, lint, segurança, padrões)</li>
<li><strong>CircleCI</strong> (build, testes, análise estática, push no ECR)</li>
<li><strong>AWS ECR</strong> (registry de imagens)</li>
<li><strong>Jenkins</strong> (orquestração do deploy)</li>
<li><strong>Amazon EKS</strong> (execução final das aplicações)</li>
</ul>
<h2 id="como-fazemos">Como fazemos</h2>
<p>Buscamos ao longo dos anos, um fluxo confiável e robusto de entrega contínua.</p>
<ol>
<li>O dev abre um <strong>Pull Request</strong> no GitHub.</li>
<li>O <strong>Copilot AI</strong> sugere melhorias de código, segurança, testes e estilo. Além de outras pessoas desenvolvedoras.</li>
<li>O PR é revisado com regras rígidas e padronizadas.</li>
<li>Ao receber <em>approve</em>, o <strong>merge</strong> dispara pipelines no CircleCI.</li>
<li>O CircleCI:
<ul>
<li>roda testes</li>
<li>executa linting</li>
<li>builda a imagem Docker</li>
<li>faz push no <strong>AWS ECR</strong></li>
<li>chama o <strong>Jenkins via CLI</strong></li>
</ul>
</li>
<li>O Jenkins executa o pipeline de deploy:
<ul>
<li>baixa a nova imagem</li>
<li>aplica YAML ou roda Helm</li>
<li>valida readiness</li>
<li>realiza rollback automático se necessário</li>
</ul>
</li>
<li>A aplicação é atualizada no <strong>EKS</strong>.</li>
</ol>
<p>Resultado: deploys previsíveis, auditáveis e rastreáveis.</p>
<h2 id="por-que-cicd-é-um-pilar-do-cloud-native">Por que CI/CD é um pilar do cloud-native</h2>
<p>Cloud-native não significa simplesmente &ldquo;estar na nuvem&quot;s. Significa projetar e operar sistemas que exploram a nuvem ao máximo. Tendo autonomia e facilidade para entrega contínua de soluções.</p>
<p>Em uma filosofia Cloud-native exige:</p>
<ul>
<li><strong>deploys frequentes</strong></li>
<li><strong>rollback instantâneo</strong></li>
<li><strong>ambientes efêmeros</strong></li>
<li><strong>consistência entre ambientes</strong></li>
<li><strong>imagens imutáveis</strong></li>
<li><strong>infra em YAML ou Helm</strong></li>
<li><strong>zero intervenção manual</strong></li>
</ul>
<p>Na minha visão, sem um pipeline maduro, Kubernetes vira risco, não vantagem.</p>
<h2 id="visão-geral-da-arquitetura-do-nosso-pipeline">Visão geral da arquitetura do nosso pipeline</h2>
<p>Fluxo completo:</p>
<p>GitHub → Pull Request → Copilot AI → Code Review → Merge → CircleCI → Build Docker → Scan → Push ECR → Trigger Jenkins → Deploy no EKS → Observabilidade</p>
<p><strong>Pontos fundamentais:</strong></p>
<ul>
<li>Um PR <strong>nunca</strong> vira deploy sem revisão humana.</li>
<li>Toda imagem tem <strong>tag semântica + tag por SHA</strong>.</li>
<li>Todo deploy é rastreável até o commit original.</li>
</ul>
<h2 id="github--copilot-ai-a-primeira-linha-de-defesa-e-automação">GitHub + Copilot AI: a primeira linha de defesa e automação</h2>
<p>O GitHub atua como centro de controle:</p>
<h4 id="-branching-model-padrão">✔️ Branching model padrão</h4>
<ul>
<li><code>main</code> → produção</li>
<li><code>develop</code> → staging (estamos em constante avanço nessa parte)</li>
<li>branches de feature: <code>feature/nome</code></li>
<li>hotfixes controlados por tag</li>
</ul>
<h4 id="-o-copilot-ajuda-no-pr">✔️ O Copilot ajuda no PR</h4>
<p>Usamos o Copilot para:</p>
<ul>
<li>detectar código inseguro</li>
<li>revisar padrões de estilo</li>
<li>identificar endpoints sem cobertura</li>
<li>explicar diffs complexos durante code review</li>
</ul>
<p>Ele não substitui a revisão humana, mas agiliza muito.</p>
<h2 id="template-de-pull-request-padrão-harmo">Template de Pull Request (padrão Harmo)</h2>
<p>Usamos um PR <em>enxuto, objetivo e baseado em engenharia madura</em>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-markdown" data-lang="markdown"><span style="display:flex;"><span><span style="font-weight:bold">## 🔍 Descrição
</span></span></span><span style="display:flex;"><span>O que foi alterado? Por quê?
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="font-weight:bold">## 🎯 Motivação / Contexto
</span></span></span><span style="display:flex;"><span>Qual problema isso resolve?
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="font-weight:bold">## 📊 Observabilidade
</span></span></span><span style="display:flex;"><span><span style="color:#ff79c6">- [ ]</span> Logs ajustados
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">- [ ]</span> Métricas adicionadas/alteradas
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">- [ ]</span> Dashboards/alerts impactados?
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="font-weight:bold">## 💰 Impacto em Custos
</span></span></span><span style="display:flex;"><span><span style="color:#ff79c6">- [ ]</span> Avaliado (I/O, memória, storage, execuções, banda)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="font-weight:bold">## 🚀 Deploy
</span></span></span><span style="display:flex;"><span>Serviço / namespace afetado:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="font-weight:bold">## 🔙 Rollback
</span></span></span><span style="display:flex;"><span>Como desfazer? (passo a passo)
</span></span></code></pre></div><h2 id="circleci-build-scan-e-push-para-o-ecr">CircleCI: build, scan e push para o ECR</h2>
<p>Quando o PR é aprovado e mergeado, pipeline CircleCI executa:</p>
<ul>
<li>Checkout do código</li>
<li>Lint + análise estática</li>
<li>Testes unitários</li>
<li>Build da imagem Docker</li>
</ul>
<p>Tag da imagem com:</p>
<ul>
<li>versão semântica</li>
<li>commit SHA</li>
<li>timestamp</li>
<li>Push para o AWS ECR</li>
</ul>
<p>Chamada ao Jenkins:</p>
<p>Temos um repositório interno de CLI, ele é clonado durante o build, e o jenkins é acionado via bash, usando a API do jenkins.</p>
<h2 id="jenkins-deploy-seguro-no-eks">Jenkins: deploy seguro no EKS</h2>
<p>O Jenkins recebe o trigger e roda o pipeline de deploy:</p>
<ul>
<li>Baixar imagem do ECR</li>
<li>Edita os arquivos helm</li>
<li>Atualiza o EKS/pods</li>
</ul>
<p>Utilizando a flag <code>--atomic</code>, o Helm reverte sozinho se algo falhar.</p>
<h2 id="rastreabilidade-e-confiabilidade">Rastreabilidade e confiabilidade</h2>
<p>Toda versão na produção possui:</p>
<ul>
<li>referência ao SHA do commit</li>
<li>tag exata da imagem</li>
<li>ambiente, namespace e cluster</li>
<li>pipeline de origem (CircleCI job ID)</li>
<li>logs de deploy no Jenkins</li>
<li>métricas de latência pós-deploy</li>
</ul>
<p>Isso facilita postmortems (fica pra um próximo artigo), auditorias e diagnósticos.</p>
<h2 id="benefícios-reais">Benefícios reais</h2>
<ul>
<li>Deploys rápidos e previsíveis</li>
<li>Zero intervenção manual</li>
<li>Menos incidentes</li>
<li>Versionamento rigoroso</li>
<li>Segurança reforçada</li>
<li>Redução drástica de falhas humanas</li>
<li>Replicabilidade entre ambientes</li>
<li>Cultura de engenharia madura</li>
<li>Time mais rápido e com mais confiança</li>
</ul>
<h2 id="conclusão">Conclusão</h2>
<p>CI/CD não é um acessório, é a coluna vertebral de uma plataforma cloud-native.
Sem automação, sem padrões e sem disciplina, Kubernetes se torna uma fábrica de riscos.</p>
<p>Ao integrar GitHub, Copilot, CircleCI, ECR, Jenkins e EKS, criamos um pipeline:</p>
<ul>
<li>seguro</li>
<li>rastreável</li>
<li>escalável</li>
<li>automatizado</li>
<li>de fácil auditoria</li>
<li>e resiliente a falhas</li>
</ul>
<p>Esse pipeline permite que a Harmo evolua sua plataforma sem medo de quebrar produção, com velocidade de startup e qualidade de empresa enterprise.
s
E quais são suas experiências em CI/CD? Compartilha conosco nos comentários.</p>
]]></content:encoded><category>cloud</category><category>devops</category><category>aws</category><category>engenharia</category><category>automação</category></item><item><title>Como adicionar dicionários no ClickUp</title><link>https://rifeli.dev/blog/como-adicionar-dicionarios-no-clickup/</link><pubDate>Tue, 28 Oct 2025 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/como-adicionar-dicionarios-no-clickup/</guid><description>Aprenda a ativar e configurar dicionários personalizados no ClickUp para evitar erros de digitação e melhorar a comunicação entre equipes.</description><content:encoded><![CDATA[<img id="image-custom" src="https://clickup.com/images/clickup-v3/CU_3.0_Task_Types.png" alt="ClickUP Platform" />
<p id="image-legend">ClickUp</p>
<h2 id="introdução">Introdução</h2>
<p>Aqui na <strong>Harmo</strong>, migramos do Slack para o Discord em 2022. A mudança parecia ideal no início, afinal, conseguimos liberdade, integração com bots e boa performance.<br>
Mas, com o tempo, os desafios de <strong>governança, histórico e centralização da informação</strong> começaram a pesar. Precisávamos de uma ferramenta que reunisse tudo: chat, tarefas, docs, dashboards e gestão, sem o custo de um carro por mês.</p>
<p>Foi aí que chegamos ao <strong>ClickUp</strong>.
E não, este artigo não é patrocinado 😄, é apenas o relato de quem usa intensamente a ferramenta e descobriu alguns <em>truques</em> úteis, como <strong>ativar dicionários personalizados</strong> para escrever sem erros (e sem perder o português).</p>
<p>Afinal, sempre uso OS em US.</p>
<h2 id="o-clickup-como-central-de-operações">O ClickUp como central de operações</h2>
<p>O ClickUp se posiciona como uma plataforma &ldquo;tudo-em-um&rdquo; para produtividade de times.<br>
A ideia é simples: <strong>um único lugar para conversar, planejar, fazer calls (isso é muit massa), documentar e acompanhar tudo o que acontece</strong> dentro da empresa. Também tendo a opção de conectar tudo isso com modelos LLM.</p>
<p>Na Harmo, usamos o ClickUp como nossa central de operações para praticamente tudo:</p>
<ul>
<li>Comunicação assíncrona entre times técnicos e de negócio;</li>
<li>Reuniões (calls) com o time, sempre com gravação e histórico centralizado;</li>
<li>Planejamento semanal e roadmaps por área;</li>
<li>Documentação interna e repositórios de conhecimento;</li>
<li>Gestão de demandas e integrações automáticas com GitHub, AWS e Google Drive;</li>
<li>Alertas de engenharia e notificações automáticas (deploys, falhas, métricas);</li>
<li>Análises semanais com IA, enviadas automaticamente nos canais oficiais de cada área (Suporte, Engenharia,Produto, etc.), trazendo uma retrospectiva visual e interpretável dos principais indicadores.</li>
</ul>
<p>Mas, ao instalar o aplicativo desktop Linux (o AppImage oficial), percebi um pequeno detalhe: o <strong>dicionário de correção automática</strong> vem apenas em inglês, e não há um menu visível para adicionar outros idiomas.<br>
Foi o início de uma pequena investigação técnica. Afinal, ele é um Electron rodando a aplicação web 😄.</p>
<h2 id="o-problema-autocorreção-em-inglês">O problema: autocorreção em inglês</h2>
<p>Se você escreve em português dentro do ClickUp, especialmente em <em>docs</em> ou comentários, já deve ter notado:</p>
<blockquote>
<p>Tudo fica sublinhado em vermelho, inclusive palavras corretas (pra mim é depressivo isso haha).</p>
</blockquote>
<p>Isso acontece porque o aplicativo desktop do ClickUp (assim como muitos <em>Electron apps</em>) usa o dicionário <strong>do Chromium embutido</strong>, e <strong>não herda automaticamente o dicionário do sistema</strong>.</p>
<p>Ou seja: mesmo que o seu sistema operacional esteja em português, o ClickUp ignora isso.</p>
<h2 id="a-solução-adicionando-dicionários-manualmente">A solução: adicionando dicionários manualmente</h2>
<p>Depois de algumas horas de experimentos (e erros), chegamos à solução definitiva para o <strong>Linux</strong>, que também funciona com pequenas variações no <strong>Windows</strong> e <strong>macOS</strong>.</p>
<h4 id="-passo-a-passo-linux">🔧 Passo a passo (Linux)</h4>
<ol>
<li><strong>Localize a pasta de configuração do ClickUp:</strong></li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>~/.config/ClickUp
</span></span></code></pre></div><ol start="2">
<li><strong>Crie (ou edite) o arquivo <code>Preferences</code></strong> e adicione a configuração de idioma:</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>vim ~/.config/ClickUp/Preferences
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>   <span style="color:#ff79c6">&#34;migrated_user_scripts_toggle&#34;</span>:<span style="color:#ff79c6">true</span>,
</span></span><span style="display:flex;"><span>   <span style="color:#ff79c6">&#34;spellcheck&#34;</span>:{
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">&#34;dictionaries&#34;</span>:[
</span></span><span style="display:flex;"><span>         <span style="color:#f1fa8c">&#34;en-US&#34;</span>,
</span></span><span style="display:flex;"><span>         <span style="color:#f1fa8c">&#34;pt-BR&#34;</span>
</span></span><span style="display:flex;"><span>      ],
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">&#34;dictionary&#34;</span>:<span style="color:#f1fa8c">&#34;&#34;</span>
</span></span><span style="display:flex;"><span>   }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><ol start="3">
<li><strong>Baixe o dicionário <code>pt-BR</code> para Chromium</strong></li>
</ol>
<p>No meu caso, só de instalar o pacote do idioma no OS, já funcionou perfeitamente.</p>
<ol start="4">
<li><strong>Reinicie o aplicativo.</strong></li>
</ol>
<p>Agora o ClickUp reconhecerá automaticamente o novo idioma, e o corretor deixará de marcar todas as palavras em português como erro. E n omeu caso, mantenho o ClickUp em en-US, mas ele consegue validar idioma pt-BR na escrita também.</p>
<img id="image-custom" src="/images/posts/clickup.png" alt="ClickUP Platform" />
<p id="image-legend">ClickUp rodando em en-US, validando pt-BR</p>
<h2 id="valores">Valores</h2>
<p>Aqui, conseguimos diversos contatos no BR para intermediar as assinaturas, mas como não temos tantas assinaturas (30 no total), resolvemos fazer direto com eles. Pagando o pricing atualizado de assinatura mensal.</p>
<h2 id="conclusão">Conclusão</h2>
<p>Pequenos detalhes fazem grande diferença no dia a dia de times técnicos.<br>
Ao ativar o dicionário correto no ClickUp, eliminamos ruído visual, reduzimos erros em <em>docs</em> e ganhamos agilidade na comunicação escrita, especialmente em empresas com fluxo intenso de documentação, como a Harmo.</p>
<p>Essa configuração simples é um ótimo exemplo de como <strong>ajustes técnicos aparentemente pequenos</strong> refletem <strong>em produtividade e clareza organizacional</strong>.</p>
<p>E se você também vive no ecossistema Linux ou busca uma alternativa mais governável ao Slack/Discord, vale explorar o ClickUp com carinho, com dicionário em português e tudo 😄</p>
]]></content:encoded><category>gestão</category><category>chat</category></item><item><title>Por que priorizar o fetch nativo? E como implementar timeout nas requisições</title><link>https://rifeli.dev/blog/2025-08-20-por-que-usar-o-fetch-no-nodejs/</link><pubDate>Wed, 20 Aug 2025 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/2025-08-20-por-que-usar-o-fetch-no-nodejs/</guid><description>Entenda por que o fetch nativo deve ser sua primeira escolha no Node.js e veja como implementar timeout de forma simples e eficiente.</description><content:encoded><![CDATA[<h2 id="introdução">Introdução</h2>
<img id="image-custom" src="https://nodejs.org/static/logos/nodejsDark.svg" alt="Logotipo do Node.js" />
<p id="image-legend">Node.js</p>
<p>Na Harmo, nosso codebase é bastante diverso. Sempre buscamos equilíbrio entre as linguagens, usando cada uma para aquilo que faz de melhor. Começamos nosso core em <strong>Node.js</strong> e, desde 2017, evoluímos muito com <strong>Golang</strong> (desde a versão 1.7). Hoje, também contamos com Python, Vue.js e até shell scripts para infra.</p>
<p>No ecossistema <strong>Node.js</strong> é comum haver múltiplas formas de resolver o mesmo problema. Por isso, adotamos uma diretriz clara: <strong>sempre priorizar o que é nativo</strong>, evitando dependências externas desnecessárias. Quando precisamos de algo mais específico, criamos pacotes privados que abstraem essa lógica, garantindo reaproveitamento e segurança.</p>
<p>Dessa forma, para gerir os clients back-end que fazem requisições HTTPS (internas e externas), usamos desde o Node v18 o <strong>fetch</strong> nativo. Porém, ele não possui uma forma &ldquo;oficial” de configurar timeout. Vamos ver como contornar isso.</p>
<img id="image-custom" src="/images/posts/nodejs-fetch/distribuicao_tecnologias.png" alt="Gráfico de pizza da distribuição de linguagens: Golang 40%, Python 24%, Node.js 20%, VueJS 10% e Shell mais infra 6%" />
<p id="image-legend">Distribuição das linguagens no codebase da Harmo</p>
<h2 id="por-que-usar-fetch-nativo">Por que usar fetch nativo?</h2>
<p>A primeira pergunta que sempre aparece é: <em>&ldquo;Por que usar algo nativo e contornar essa limitação, se eu posso importar o Axios e setar <code>timeout</code> facilmente?”</em>.</p>
<h4 id="pontos-que-pesam-a-favor-do-fetch">Pontos que pesam a favor do fetch:</h4>
<ul>
<li><strong>Livre de dependências externas</strong>: menos pacotes para atualizar, menos riscos de vulnerabilidades.</li>
<li><strong>Adoção e padronização</strong>: hoje é API estável, padronizada e suportada oficialmente no Node 18+.</li>
<li><strong>API moderna</strong>: suporta streaming, trabalha com Promises e se alinha ao padrão da Web.</li>
</ul>
<h4 id="comparativo-mais-amplo--fetch-vs-axios">Comparativo mais amplo — fetch vs Axios</h4>
<ol>
<li>
<p><strong>Zero dependências, menos superfície de risco</strong></p>
<ul>
<li>Nada de instalar libs só para HTTP.</li>
<li>Menos código de terceiros no deploy, menos CVEs para monitorar.</li>
</ul>
</li>
<li>
<p><strong>Padrão da Web (WHATWG) e futuro-proof</strong></p>
<ul>
<li>Mesma API usada em navegadores, Deno, Bun, runtimes serverless/edge.</li>
<li>Isomórfico: o mesmo código funciona em front e back.</li>
</ul>
</li>
<li>
<p><strong>Disponível nativamente no Node 18+</strong></p>
<ul>
<li>Já faz parte do runtime, baseado em <em>undici</em>.</li>
<li>Suporte a <code>AbortController</code> e <code>AbortSignal.timeout(ms)</code>.</li>
</ul>
</li>
<li>
<p><strong>API moderna e composável</strong></p>
<ul>
<li><code>Request</code>, <code>Response</code>, <code>Headers</code> padronizados.</li>
<li>Composição simples via funções, sem precisar de interceptors acoplados.</li>
</ul>
</li>
<li>
<p><strong>Performance e simplicidade operacional</strong></p>
<ul>
<li>Menos camadas ⇒ overhead menor.</li>
<li>Tree-shaking natural: usa a API do runtime, não importa um bundle extra.</li>
</ul>
</li>
</ol>
<hr>
<h2 id="e-quando-faz-sentido-usar-axios-ou-outra-lib">E quando faz sentido usar Axios (ou outra lib)?</h2>
<p>Não é sobre &ldquo;nunca usar Axios”, mas sim <strong>começar com fetch</strong> e só adicionar uma lib se o caso justificar.</p>
<p><strong>Exemplos em que pode valer a pena:</strong></p>
<ul>
<li>Interceptors prontos para logging, métricas ou autenticação.</li>
<li>Retries/backoff já implementados.</li>
<li>Serialização automática de <code>params</code> e normalização de erros.</li>
<li>Ambientes legados (Node &lt; 18).</li>
<li>Ergonomia de configuração (<code>axios.create</code>, defaults globais, etc.).</li>
</ul>
<hr>
<h2 id="problema-de-timeout-e-helpers">Problema de Timeout e helpers</h2>
<p>O <strong>fetch</strong> não tem uma opção <code>timeout</code> nativa no objeto de configuração.
Se você não tratar isso, uma requisição pode ficar pendurada indefinidamente, travando fluxo e degradando UX.</p>
<h4 id="solução-1-abortcontroller--settimeout">Solução 1: AbortController + setTimeout</h4>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#ff79c6">async</span> <span style="color:#8be9fd;font-style:italic">function</span> fetchWithTimeout(url, options <span style="color:#ff79c6">=</span> {}, timeout <span style="color:#ff79c6">=</span> <span style="color:#bd93f9">5000</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">const</span> controller <span style="color:#ff79c6">=</span> <span style="color:#ff79c6">new</span> AbortController();
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">const</span> timer <span style="color:#ff79c6">=</span> setTimeout(() =&gt; controller.abort(), timeout);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">try</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">return</span> <span style="color:#ff79c6">await</span> fetch(url, { ...options, signal<span style="color:#ff79c6">:</span> controller.signal });
</span></span><span style="display:flex;"><span>  } <span style="color:#ff79c6">finally</span> {
</span></span><span style="display:flex;"><span>    clearTimeout(timer);
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h4 id="solução-2-abortsignaltimeout-mais-moderna">Solução 2: AbortSignal.timeout (mais moderna)</h4>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#ff79c6">const</span> response <span style="color:#ff79c6">=</span> <span style="color:#ff79c6">await</span> fetch(<span style="color:#f1fa8c">&#34;https://api.exemplo.com&#34;</span>, {
</span></span><span style="display:flex;"><span>  signal<span style="color:#ff79c6">:</span> AbortSignal.timeout(<span style="color:#bd93f9">3000</span>),
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><h2 id="conclusão">Conclusão</h2>
<p>Como trabalhamos com microsserviços e serverless, estamos sempre atualizando nosso codebase e priorizando recursos nativos.</p>
<ul>
<li><strong>O fetch nativo deve ser sempre a primeira escolha:</strong> leve, padronizado e futuro-proof;</li>
<li><strong>Timeout não é um problema:</strong> com AbortController ou AbortSignal.timeout, você cobre 99% dos cenários;</li>
<li>Axios e outras libs ainda têm espaço, mas só quando há uma necessidade clara (interceptors complexos, retrys prontos, ergonomia global).</li>
</ul>
<h2 id="lições-aprendidas">Lições aprendidas</h2>
<p>Interessante refletir e analisar cada caso, mas o que sempre buscamos ter aqui é o básico bem feito.</p>
<ul>
<li><strong>Comece simples:</strong> fetch nativo atende a maioria dos casos;</li>
<li><strong>Priorize o controle:</strong> implemente timeout sempre, mesmo em chamadas internas;</li>
<li><strong>Reduza dependências:</strong> cada pacote a menos significa menos riscos, menos manutenção e menos bugs;</li>
<li><strong>Crie seus utilitários:</strong> pequenos helpers (fetchJson, fetchWithRetry) podem substituir dezenas de linhas de configs em libs externas;</li>
<li><strong>Padronize no time:</strong> uma decisão simples como <strong>“usar fetch nativo com helper padrão”</strong> já elimina inconsistências e acelera onboarding de devs.</li>
</ul>
<p><strong>💬 E você, como lida com requisições no Node.js?</strong></p>
<p>Prefere ficar só no fetch nativo ou ainda vê valor em libs como Axios?
Deixa sua experiência nos comentários, quero muito ouvir como outros times têm tratado esse tema no dia a dia.</p>
]]></content:encoded><category>nodejs</category><category>fetch</category><category>timeout</category><category>código</category><category>nativo</category></item><item><title>Do zero a Home Server: como e por que montei meu NAS em ZFS</title><link>https://rifeli.dev/blog/2025-08-10-do-zero-a-home-server-como-e-por-que-montei-meu-nas-em-zfs/</link><pubDate>Sun, 10 Aug 2025 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/2025-08-10-do-zero-a-home-server-como-e-por-que-montei-meu-nas-em-zfs/</guid><description>Comecei a programar aos 14 anos com um site de streaming de áudio e um servidor de Tibia. Desde então, soluções self-hosted sempre fizeram parte da minha vida, permitindo aprender e experimentar. Agora, depois de quase dez anos, montei um home server moderno, com NAS em ZFS, Docker e Cloudflare Tunnel.</description><content:encoded><![CDATA[<h2 id="introdução">Introdução</h2>
<img id="image-custom" src="/images/posts/home-server/19.png" alt="Tampa de notebook coberta de adesivos sobre uma prateleira de madeira, com o gabinete do servidor iluminado logo abaixo" />
<p id="image-legend">Sempre em busca da melhor versão</p>
<p>Comecei a programar aos 14 anos, tendo um site de streaming de áudio e um servidor de Tibia (vulgo OT Server), na época rodando na versão 8.5. Desde cedo, <strong>soluções self-hosted</strong> foram parte essencial do meu aprendizado: de protocolos de comunicação até sistemas de auto-gestão.</p>
<p>Isso se estendeu até em uma solução pessoal para gestão de finanças, também self-hosted, feita em PHP. <a href="https://github.com/leonardorifeli/morfeu">Dá um visu aqui</a>.</p>
<p>Depois de quase dez anos, decidi montar novamente meu próprio <strong>home server</strong> para centralizar arquivos, ter mais controle e segurança, expandir conhecimentos em infraestrutura e substituir serviços pagos por alternativas self-hosted.</p>
<p><strong>O resultado foi um ambiente poderoso:</strong></p>
<ul>
<li><strong>NAS em ZFS (5TB)</strong></li>
<li>Containers Docker gerenciados via <strong>Portainer</strong></li>
<li>Interface web para gestão dos arquivos</li>
<li>Acesso externo seguro para serviços e SSH via <strong>Cloudflare Tunnel</strong></li>
</ul>
<hr>
<h2 id="o-que-é-nas-e-por-que-usar-raidzfs">O que é NAS e por que usar RAID/ZFS?</h2>
<p>Antes de mergulhar na prática, vale entender alguns conceitos fundamentais.</p>
<h4 id="-o-que-é-nas">📦 O que é NAS?</h4>
<p><strong>NAS (Network Attached Storage)</strong> é basicamente um servidor de arquivos conectado à rede.</p>
<ul>
<li>Funciona como uma <strong>central de armazenamento</strong>, acessível de qualquer dispositivo (PC, notebook, celular, TV etc).</li>
<li>Diferente de um HD externo, o NAS está sempre disponível, seja localmente ou pela internet (no nosso caso, via <strong>Cloudflare Tunnel</strong>).</li>
<li>É comum em empresas, mas também faz todo sentido em casa, para fotos, vídeos, documentos ou até para hospedar <strong>Nextcloud</strong> ou <strong>Jellyfin/Plex</strong>.</li>
</ul>
<h4 id="-o-que-é-raid">🔄 O que é RAID?</h4>
<p><strong>RAID (Redundant Array of Independent Disks)</strong> combina múltiplos discos em um volume lógico, trazendo:</p>
<ul>
<li><strong>Redundância</strong>: se um disco falhar, os dados continuam acessíveis.</li>
<li><strong>Performance</strong>: alguns níveis distribuem dados entre discos, acelerando leitura/escrita.</li>
</ul>
<p><strong>Exemplos:</strong></p>
<ul>
<li><strong>RAID 1</strong>: espelhamento — dois discos, um copia o outro.</li>
<li><strong>RAID 5</strong>: três ou mais discos, com paridade (suporta falha de 1 disco).</li>
<li><strong>RAID 6</strong>: como o RAID 5, mas suporta falha de até 2 discos.</li>
</ul>
<p>⚠️ <strong>Importante</strong>: RAID não substitui backup. Ele apenas protege contra falhas físicas. (aprendi isso da pior forma 😅)</p>
<h4 id="-e-onde-entra-o-zfs">🛡️ E onde entra o ZFS?</h4>
<p>O <strong>ZFS (Zettabyte File System)</strong> combina <strong>filesystem + RAID + volume manager</strong> em uma só solução.</p>
<p>Ele oferece:</p>
<ul>
<li><strong>Integridade de dados</strong>: cada bloco tem checksum, evitando corrupções silenciosas.</li>
<li><strong>Snapshots e clones</strong>: restauração rápida de versões anteriores.</li>
<li><strong>Compressão transparente</strong>: economiza espaço automaticamente.</li>
<li><strong>Escalabilidade</strong>: projetado para petabytes de dados (eu sigo com 5TB, por enquanto).</li>
</ul>
<p>No meu setup, uso <strong>ZFS com RAID-Z</strong> (semelhante ao RAID 5), que equilibra espaço, performance e tolerância a falhas.</p>
<hr>
<h2 id="o-que-é-cloudflare-e-por-que-usei-no-meu-home-server">O que é Cloudflare e por que usei no meu Home Server</h2>
<p>Quando expomos serviços caseiros na internet, temos duas grandes preocupações:</p>
<ul>
<li><strong>Segurança</strong> (não deixar a rede vulnerável)</li>
<li><strong>Praticidade</strong> (dispensar configurações complexas de NAT/IP fixo)</li>
</ul>
<p>É aí que entra o <strong>Cloudflare</strong>.</p>
<h4 id="-o-que-é-cloudflare">🌍 O que é Cloudflare?</h4>
<p>O <strong>Cloudflare</strong> é famoso pelo <strong>CDN</strong> e <strong>proteção contra DDoS</strong>, mas também oferece recursos que facilitam o self-hosting, como o <strong>Cloudflare Tunnel</strong>.</p>
<h4 id="-o-que-é-cloudflare-tunnel">🚇 O que é Cloudflare Tunnel?</h4>
<p>O <strong>Cloudflare Tunnel</strong> cria um túnel seguro entre meu servidor e a rede Cloudflare.</p>
<p><strong>Benefícios:</strong></p>
<ul>
<li>Nada de abrir portas no roteador</li>
<li>Não preciso de IP fixo</li>
<li>Meus serviços ficam acessíveis via domínio com <strong>HTTPS válido automático</strong></li>
</ul>
<p><strong>Exemplo prático:</strong></p>
<ul>
<li><code>https://files.seudominio.com</code> → File Browser (porta 8080)</li>
<li><code>https://portainer.seudominio.com</code> → Portainer (porta 9443)</li>
</ul>
<p>Tudo isso sem precisar configurar NGINX ou Let&rsquo;s Encrypt manualmente.</p>
<h4 id="-por-que-escolhi-o-cloudflare">🎯 Por que escolhi o Cloudflare?</h4>
<ul>
<li><strong>Facilidade:</strong> poucos comandos e já estava acessível.</li>
<li><strong>Segurança:</strong> tráfego criptografado fim a fim.</li>
<li><strong>Custo baixo:</strong> plano gratuito cobre bem (o Tunnel avançado custa ~US$10/mês).</li>
<li><strong>Escalabilidade:</strong> novos serviços são só uma linha no <code>config.yml</code>.</li>
</ul>
<hr>
<h2 id="setup">Setup</h2>
<img id="image-custom" src="/images/posts/home-server/18.png" alt="Interior do gabinete do servidor com placa-mãe, cooler, ventoinhas RGB e um boneco do Gopher do Go" />
<p id="image-legend">Server com AMD-A8-5600k, 16GB RAM e NAS 5TB</p>
<h4 id="-servidor">🖥️ Servidor</h4>
<ul>
<li><strong>CPU</strong>: AMD-A8-5600k</li>
<li><strong>RAM</strong>: 16 GB</li>
<li><strong>NAS</strong>: pool ZFS de múltiplos HDs (5 TB)</li>
<li><strong>SO</strong>: Ubuntu Server 24.10</li>
</ul>
<p>Os HDs estão ligados via <strong>gaveta hot-swap</strong>, e não diretamente à placa-mãe.</p>
<p><strong>Vantagens:</strong></p>
<ul>
<li>🔄 Troca rápida de discos defeituosos</li>
<li>📦 Expansão simples (basta adicionar novo HD)</li>
<li>🛡️ Perfeito para ZFS/RAID, com reconstrução automática (resilver)</li>
</ul>
<img id="image-custom" src="/images/posts/home-server/17.png" alt="Gaveteiro hot-swap do NAS com quatro baias e LEDs verdes indicando HD1 a HD4" />
<p id="image-legend">NAS com gavetas hot-swap</p>
<h4 id="-gestão-de-energia">⚡ Gestão de energia</h4>
<p>Para mitigar quedas de energia, configurei:</p>
<ul>
<li><strong>Gerador</strong>: 3.75 kVA 3000W</li>
<li><strong>Nobreak SMS Lite 1200VA</strong> (~6h autonomia)</li>
<li><strong>Nobreak SMS NET 4+ 1400VA</strong> (~8h autonomia)</li>
</ul>
<h4 id="-custo-de-eletricidade">💰 Custo de eletricidade</h4>
<table>
  <thead>
      <tr>
          <th>Potência média</th>
          <th style="text-align: right">kWh/mês</th>
          <th style="text-align: right">Custo/mês (R$ 1,00/kWh)</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>80 W</td>
          <td style="text-align: right">57,6</td>
          <td style="text-align: right"><strong>R$ 57,60</strong></td>
      </tr>
      <tr>
          <td>100 W</td>
          <td style="text-align: right">72,0</td>
          <td style="text-align: right"><strong>R$ 72,00</strong></td>
      </tr>
      <tr>
          <td>150 W</td>
          <td style="text-align: right">108,0</td>
          <td style="text-align: right"><strong>R$ 108,00</strong></td>
      </tr>
      <tr>
          <td>200 W</td>
          <td style="text-align: right">144,0</td>
          <td style="text-align: right"><strong>R$ 144,00</strong></td>
      </tr>
  </tbody>
</table>
<p>No meu caso:</p>
<ul>
<li><strong>Idle/leve</strong>: 80–120 W</li>
<li><strong>Carga moderada</strong>: 150–200 W</li>
</ul>
<p>Em média, gasto <strong>R$ 60–110/mês</strong>, dependendo da carga e da tarifa.</p>
<hr>
<h2 id="arquitetura">Arquitetura</h2>
<img id="image-custom" src="/images/posts/home-server/flow.jpeg" alt="Diagrama do fluxo: do usuário e do navegador para a internet, passando pela Cloudflare até o servidor e o NAS em casa" />
<p id="image-legend">Arquitetura do Home Server</p>
<h4 id="-como-funciona-o-fluxo">🔍 Como funciona o fluxo</h4>
<ol>
<li><strong>Usuário</strong> → acessa pelo navegador</li>
<li><strong>Internet</strong> → tráfego normal pela rede pública</li>
<li><strong>Cloudflare Tunnel</strong> → cria ponte segura sem NAT/IP fixo</li>
<li><strong>Servidor (Ubuntu 24.10)</strong> → roda Docker + ZFS</li>
<li><strong>NAS (ZFS Pool)</strong> → armazena dados com redundância e snapshots</li>
</ol>
<h4 id="-benefícios">✅ Benefícios</h4>
<ul>
<li><strong>Segurança:</strong> IP residencial não exposto</li>
<li><strong>Flexibilidade:</strong> qualquer container pode virar subdomínio</li>
<li><strong>Resiliência:</strong> ZFS garante integridade dos dados</li>
<li><strong>Simplicidade:</strong> acesso com domínio e SSL válido</li>
</ul>
<hr>
<h2 id="-dicas">💡 Dicas</h2>
<h4 id="-segurança">🔐 Segurança</h4>
<ul>
<li><strong>Ative o fail2ban</strong> para proteger o SSH:
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo apt install fail2ban -y
</span></span><span style="display:flex;"><span>sudo systemctl <span style="color:#8be9fd;font-style:italic">enable</span> fail2ban
</span></span><span style="display:flex;"><span>sudo systemctl start fail2ban
</span></span></code></pre></div>Configuração básica:
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-ini" data-lang="ini"><span style="display:flex;"><span><span style="color:#ff79c6">[sshd]</span>
</span></span><span style="display:flex;"><span><span style="color:#50fa7b">enabled</span> <span style="color:#ff79c6">=</span> <span style="color:#f1fa8c">true</span>
</span></span><span style="display:flex;"><span><span style="color:#50fa7b">port</span>    <span style="color:#ff79c6">=</span> <span style="color:#f1fa8c">ssh</span>
</span></span><span style="display:flex;"><span><span style="color:#50fa7b">filter</span>  <span style="color:#ff79c6">=</span> <span style="color:#f1fa8c">sshd</span>
</span></span><span style="display:flex;"><span><span style="color:#50fa7b">logpath</span> <span style="color:#ff79c6">=</span> <span style="color:#f1fa8c">/var/log/auth.log</span>
</span></span><span style="display:flex;"><span><span style="color:#50fa7b">maxretry</span> <span style="color:#ff79c6">=</span> <span style="color:#f1fa8c">5</span>
</span></span></code></pre></div></li>
<li>Use <strong>chave SSH</strong> em vez de senha.</li>
<li><strong>Cloudflare Access</strong> (opcional): autenticação em duas etapas para subdomínios.</li>
</ul>
<h4 id="-eficiência-energética">⚡ Eficiência energética</h4>
<ul>
<li>Configure <strong>spindown</strong> para HDs pouco usados.</li>
<li>Ajuste CPU para modo <code>powersave</code>.</li>
</ul>
<h4 id="-resiliência">🛡️ Resiliência</h4>
<ul>
<li>Rode <code>zpool scrub</code> periodicamente.</li>
<li>Configure <strong>snapshots automáticos</strong> no ZFS.</li>
<li>Backup externo continua essencial.</li>
</ul>
<h4 id="-automação">🧩 Automação</h4>
<ul>
<li>Configure o <strong>cloudflared</strong> para iniciar no boot.</li>
<li>Centralize logs dos containers via <strong>Portainer</strong>.</li>
</ul>
<hr>
<h2 id="-conclusão">✅ Conclusão</h2>
<p>Montar esse home server foi mais do que um exercício técnico: foi uma forma de ter <strong>controle total dos meus dados</strong>, aprendendo tecnologias enterprise como <strong>ZFS, Docker e Cloudflare Tunnel</strong> no meu próprio ambiente.</p>
<p>Hoje centralizo arquivos, rodo containers em produção pessoal e acesso tudo de forma <strong>segura e simples pela internet</strong>, sem depender de serviços pagos ou limitados.</p>
<p>Existem desafios (manutenção, energia, configuração), mas o ganho em flexibilidade e aprendizado compensa. E o melhor: é facilmente expansível — seja com mais HDs no ZFS, novos containers ou serviços como Nextcloud e Jellyfin.</p>
<p>No fim, percebi que um <strong>NAS caseiro pode ser tão poderoso quanto soluções comerciais</strong>, com a liberdade de adaptar às minhas necessidades. 🚀</p>
<p>👉 E você, montaria o seu próprio servidor ou prefere uma solução pronta? Comenta aí!</p>
<blockquote>
<p>🏊‍♂️🚴‍♂️🏃‍♂️ “Good dream is a dream lived” — Avelino, Thiago</p>
</blockquote>
<hr>
<h2 id="-changelog">📌 Changelog</h2>
<ul>
<li><strong>2025-08-10</strong> → Versão inicial do setup publicada</li>
</ul>
]]></content:encoded><category>infra-estrutura</category><category>server</category><category>self-hosted</category></item><item><title>Por que decidi oferecer consultoria técnica?</title><link>https://rifeli.dev/blog/2025-08-02-por-que-decidi-oferecer-consultoria-tecnica/</link><pubDate>Sat, 02 Aug 2025 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/2025-08-02-por-que-decidi-oferecer-consultoria-tecnica/</guid><description>Gerenciar um time técnico em constante evolução exige mais do que domínio de ferramentas: exige clareza de propósito, boas decisões arquiteturais e foco no que realmente gera valor para o negócio. Como CTO e Co-fundador da Harmo, essa é a lente pela qual enxergo tecnologia todos os dias.</description><content:encoded><![CDATA[<h2 id="introdução">Introdução</h2>
<p>Ao longo da minha jornada como CTO da Harmo, mentor na ACATE e avaliador de startups no MIDITEC, percebi um padrão recorrente: times tecnicamente fortes enfrentando desafios estratégicos por falta de direcionamento claro. Em um mercado cada vez mais acelerado, tomar decisões técnicas sem contexto, profundidade ou visão de negócio pode custar caro, e isso me motivou a criar um espaço dedicado para ajudar quem está nesse momento decisivo.</p>
<p>Essa não é uma consultoria genérica. É algo premium, pensado para founders, tech leads e CTOs que não podem errar. Aqui, o foco é destravar gargalos reais com experiência prática, análise estratégica e conversas que vão direto ao ponto. Se você sente que chegou a hora de elevar seu produto, stack ou time técnico a um novo nível, essa pode ser a parceria que estava faltando.</p>
<h2 id="e-por-que-decidi-oferecer-consultoria-técnica">E, por que decidi oferecer consultoria técnica?</h2>
<p>Nos últimos anos, à frente da Harmo como CTO e Co-fundador, estive no centro de decisões críticas que impactaram diretamente o crescimento e a escalabilidade do nosso produto, decisões que envolvem arquitetura distribuída, engenharia de dados, IA aplicada e liderança técnica de alto nível.</p>
<p>Percebi que, embora existam muitos conteúdos técnicos por aí, são raros os momentos em que fundadores e times conseguem parar e conversar com alguém que vive esses desafios na prática, todos os dias. Alguém que, além da teoria, entende as implicações reais de uma escolha técnica no negócio. Foi isso que me motivou a abrir espaço na agenda para uma consultoria premium, direcionada, com propósito claro: <strong>destravar o que está travando você e sua operação</strong>.</p>
<p>Ao longo da minha jornada, fui construindo essa autoridade com consistência. Sou mentor na <strong>ACATE (Associação Catarinense de Tecnologia)</strong>, participo de bancas e acelerações no programa <strong>MIDITEC</strong>, um dos mais tradicionais do Brasil, e atuo como CTO de uma plataforma AI-first que processa milhões de dados por dia. Além de 15+ anos de experiência, sou formado em <strong>Ciência de Dados</strong> e tenho pós-graduação em <strong>Estatística Aplicada</strong>, o que me permite aliar visão estratégica e profundidade técnica com decisões orientadas por dados, sempre com foco em impacto.</p>
<h2 id="para-quem-é-essa-consultoria">Para quem é essa consultoria?</h2>
<p>Essa consultoria não é para quem busca fórmulas prontas ou implementações genéricas. Ela é pensada para quem está lidando com problemas reais, complexos e inadiáveis, como escalabilidade, gargalos técnicos, dúvidas arquiteturais, uso de IA com responsabilidade ou times que precisam evoluir com mais direção.</p>
<p><strong>Ela é ideal para:</strong></p>
<ul>
<li>Fundadores e CTOs que precisam tomar decisões críticas com segurança</li>
<li>Tech leads que buscam validar caminhos técnicos e destravar o roadmap</li>
<li>Startups que estão escalando rápido e precisam de um olhar externo experiente</li>
<li>Times que querem implementar IA, dados ou LLMs com base sólida e visão de produto</li>
</ul>
<h2 id="como-funciona">Como funciona?</h2>
<p>As consultorias são pagas, premium e personalizadas. Cada sessão é pensada para gerar resultado direto: mais clareza, melhores decisões e menos tempo perdido com dúvidas técnicas que te afastam daquilo que realmente importa.</p>
<p>💬 Não trabalho com fórmulas. Trabalho com contexto.
🔎 Cada caso é analisado com profundidade, sem pressa, mas com objetividade.
⚠️ Vagas limitadas, minha agenda permite apenas alguns atendimentos por mês.
📍 Vagas limitadas, apenas para quem valoriza profundidade e excelência.</p>
<p>👉 Saiba mais e solicite sua sessão: <strong><a href="https://consultoria.rifeli.dev/">consultoria.rifeli.dev</a></strong></p>
]]></content:encoded><category>carreira</category><category>gestao</category></item><item><title>Entrevista com Elton Minetto em Mais Que Senior</title><link>https://rifeli.dev/blog/2025-08-01-entrevista-mais-que-senior-com-elton-minetto/</link><pubDate>Fri, 01 Aug 2025 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/2025-08-01-entrevista-mais-que-senior-com-elton-minetto/</guid><description>Gerenciar um time técnico em constante evolução exige mais do que domínio de ferramentas: exige clareza de propósito, boas decisões arquiteturais e foco no que realmente gera valor para o negócio. Como CTO e Co-fundador da Harmo, essa é a lente pela qual enxergo tecnologia todos os dias.</description><content:encoded><![CDATA[<h2 id="introdução">Introdução</h2>
<p>Gerenciar um time técnico em constante evolução exige mais do que domínio de ferramentas: exige <strong>clareza de propósito</strong>, <strong>boas decisões arquiteturais</strong> e <strong>foco no que realmente gera valor</strong> para o negócio. Como CTO e Co-fundador da Harmo, essa é a lente pela qual enxergo tecnologia todos os dias.</p>
<p>Tive a honra de compartilhar um pouco dessa jornada na newsletter <strong><a href="https://maisquesenior.substack.com/p/leonardo-rifeli">Mais Que Senior</a></strong>, em uma entrevista conduzida com sensibilidade e profundidade por um grande amigo, <strong>Elton Minetto</strong>. Falamos sobre liderança técnica, decisões estratégicas, dicas de carreira e trajetória.</p>
<p>Neste artigo, aprofundo alguns dos pontos abordados na entrevista, detalhando nossa stack, práticas e aprendizados práticos que sustentam a operação diária da Harmo. Se você lidera times técnicos, atua com cloud ou busca boas práticas em arquitetura e gestão de infraestrutura, essa leitura é pra você.</p>
<h2 id="conte-um-pouco-sobre-sua-posição-atual-como-cto-que-tipo-de-trabalho-você-faz-geralmente">Conte um pouco sobre sua posição atual, como CTO. Que tipo de trabalho você faz geralmente?</h2>
<p>Sou CTO e Co-fundador da Harmo, uma plataforma Martech AI-first que transforma feedbacks e dados operacionais em decisões inteligentes. Minha atuação é dividida entre frentes estratégicas e operacionais (cerca de 60/40), com foco em tecnologia, produto e dados.</p>
<p>Co-lidero a arquitetura de sistemas e lidero diretamente três especialistas, incluindo um Tech Manager que conduz o time de engenharia, atualmente com sete profissionais de diferentes níveis. Acompanho de perto o roadmap de produto e continuo atuando com código, especialmente em Golang, Python e aplicações de IA, áreas em que tenho mais profundidade.</p>
<p>Também estou à frente da integração de LLMs, IA generativa e pipelines de dados em larga escala. E sigo como revisor técnico ativo: nossa média gira em torno de 50 pull-requests por sprint, com cerca de 60 microsserviços em produção, rodando em K8s e Airflow.</p>
<h2 id="que-tipo-de-impacto-você-sente-que-mais-gera-na-sua-posição-atual-e-como-isso-é-diferente-em-relação-a-sua-posição-anterior">Que tipo de impacto você sente que mais gera na sua posição atual? E como isso é diferente em relação a sua posição anterior?</h2>
<p>Hoje meu impacto é sistêmico: construo os alicerces técnicos que permitem escalar o negócio com inteligência e sustentabilidade. Diferente de posições anteriores, onde o foco era na entrega individual, agora meu papel é potencializar a performance do time como um todo, garantindo alinhamento entre tecnologia, produto e estratégia.</p>
<p>Qual é o percentual de trabalho entre código e liderança que você desempenha hoje? 50/50? Passa mais tempo trabalhando com tecnologia/codando ou mais tempo liderando pessoas?
Hoje minha atuação está quase equilibrada, com cerca de 40% dedicada ao código e 60% voltada à liderança. Sigo atuando tecnicamente por afinidade e por necessidade, especialmente nas frentes de IA, NLP e arquiteturas de dados mais complexas, mas meu foco principal está nas decisões estratégicas, no acompanhamento próximo dos times e na estruturação de processos técnicos que sustentem nosso crescimento.</p>
<h2 id="como-é-um-dia-normal-na-sua-rotina-ou-como-é-uma-semana-normal-para-você--quais-são-suas-rotinas-principais">Como é um dia normal na sua rotina? Ou como é uma semana normal para você?  Quais são suas rotinas principais?</h2>
<p>Minhas semanas são marcadas por cerimônias técnicas, revisões de arquitetura, mentorias, revisões de código e bastante atividade prática, sigo codando em frentes estratégicas, especialmente em features e arquiteturas voltadas a NLP, IA, LangChain e ML.</p>
<p>Reservo blocos de tempo para deep work técnico e acompanhamento próximo do roadmap de produto e dados. Também mantenho cadências com os especialistas do time, garantindo fluidez técnica, alinhamento com o negócio e evolução contínua da equipe.</p>
<p>Além disso, tenho rotinas regulares com os demais sócios, conselheiros e investidores, equilibrando a visão técnica com a visão de negócio e crescimento da empresa.</p>
<h2 id="como-você-mede-seu-sucesso-quando-somos-pessoas-desenvolvedoras-de-software-é-comum-medirmos-nosso-sucesso-pelo-número-de-commits-pull-requests-entregas-realizadas-isso-mudou-de-alguma-forma-em-sua-posição-atual">Como você mede seu sucesso? Quando somos pessoas desenvolvedoras de software é comum medirmos nosso sucesso pelo número de commits, pull requests, entregas realizadas. Isso mudou de alguma forma em sua posição atual?</h2>
<p>Sim, mudou bastante. Mesmo no inicio de carreira, já buscava enxergar sucesso além de horas trabalhadas, commits ou linhas de código, mas foi com o tempo e com a experiência que essa visão amadureceu.</p>
<p>Antes, eu media meu sucesso por entregas concretas: features lançadas, melhorias de performance, soluções técnicas. Hoje, minha métrica é o impacto técnico e estratégico que consigo gerar: a performance do time, a sustentabilidade das decisões de arquitetura, a velocidade de aprendizado da organização e, principalmente, o valor que a tecnologia entrega para o negócio. Além, de avaliar o impacto em custos vs crescimento comercial.</p>
<h2 id="você-participa-das-decisões-de-tecnologia-ou-arquitetura-como-gerencia-essa-influência-em-relação-aos-demais-times-você-toma-boa-parte-das-decisões-ou-guia-os-times-para-que-eles-cheguem-às-conclusões">Você participa das decisões de tecnologia ou arquitetura? Como gerencia essa influência em relação aos demais times? Você toma boa parte das decisões ou guia os times para que eles cheguem às conclusões?</h2>
<p>Sim, participo ativamente das decisões de tecnologia e arquitetura. Costumo guiar o time em discussões técnicas, sempre priorizando debates estruturados e incentivando o ownership das decisões. Evito ser o gargalo ou o &ldquo;dono da verdade&rdquo;, busco atuar como facilitador, tomando decisões quando necessário, mas sempre com base em construção coletiva.</p>
<p>Mesmo com o crescimento exponencial do negócio, acredito que a visão estratégica precisa estar presente também no nível técnico. É essa presença que garante que as decisões do dia a dia estejam alinhadas com nossa bússola de médio-longo prazo.</p>
<p>Que soft-skills você percebe que fazem a diferença na sua posição? E como eles diferem da posição anterior?
Costumo enxergar habilidades técnicas ou comportamentais em dois grupos: as treináveis e as que vêm de forma mais natural. Me esforço para desenvolver continuamente as treináveis que geram impacto direto no negócio, mas também aprendi a valorizar e canalizar as que já fazem parte do meu perfil, como criatividade e empatia.</p>
<p>Com a maturidade profissional e como empreendedor, percebi que as soft-skills passaram a ter um papel central. As que mais fazem diferença para mim hoje são: escuta ativa, clareza e objetividade na comunicação, gestão de conflitos e leitura de cenário. Antes, meu foco era resolver problemas técnicos diretamente. Hoje, minha principal missão é criar os contextos certos para que outras pessoas consigam resolver com excelência, e isso exige inteligência relacional e liderança com propósito.</p>
<h2 id="você-dedica-tempo-para-mentorar-as-demais-pessoas-dos-times">Você dedica tempo para mentorar as demais pessoas dos times?</h2>
<p>Sim, e considero isso uma das partes mais gratificantes do papel de liderança. No dia a dia, as mentorias acontecem de forma natural, nos rituais, revisões técnicas e discussões de arquitetura. Além disso, mantenho momentos dedicados de 1:1 com os especialistas e apoio diretamente profissionais mais plenos e juniores, incentivando tanto o desenvolvimento técnico quanto o comportamental.</p>
<p>Acredito que as mentorias aceleram o amadurecimento técnico, cultural e emocional do time. Sou alguém que gosta de aprender, ensinar e trocar constantemente. Para mim, o verdadeiro motor da evolução é o exemplo, e liderar pelo exemplo é o que mais inspira e transforma.</p>
<h2 id="como-cto-em-algum-momento-a-empresa-começa-a-crescer-e-é-necessário-tomar-uma-decisão-entre-a-parte-técnica-e-as-demais-demandas-de-negóciogestão-como-você-está-se-preparando-para-esse-momento-de-decisão-você-se-imagina-seguindo-pelo-caminho-técnico-ou-de-gestãonegócio">Como CTO em algum momento a empresa começa a crescer e é necessário tomar uma decisão entre a parte técnica e as demais demandas de negócio/gestão. Como você está se preparando para esse momento de decisão? Você se imagina seguindo pelo caminho técnico ou de gestão/negócio?</h2>
<p>Me vejo em uma posição híbrida, com forte raiz técnica e visão estratégica. Gosto de construir, resolver problemas complexos, transformar dados em decisões inteligentes e estruturar times de alta performance. Mesmo com o crescimento da Harmo, meu objetivo é seguir como uma liderança técnica que conecta engenharia ao negócio e impulsiona inovação com propósito.</p>
<p>Acredito que escalar também é saber colaborar. Por isso, visualizo uma parceria próxima com alguém na cadeira de VP de Engenharia, permitindo que eu mantenha o foco em arquitetura, IA e visão de produto, enquanto garantimos excelência na operação e na gestão do time.</p>
<h2 id="que-dicas-você-pode-dar-para-quem-está-decidindo-se-continua-o-caminho-de-desenvolvimento-ou-se-deve-empreender">Que dicas você pode dar para quem está decidindo se continua o caminho de desenvolvimento ou se deve empreender?</h2>
<p>Comecei a empreender aos 21 anos, após mais de cinco anos de carreira como desenvolvedor. O que aprendi até aqui é que não existe uma resposta certa ou um único caminho para o sucesso, mas sim autoconhecimento, consistência e coragem para se adaptar em cenários diversos.</p>
<p>Se você gosta de resolver problemas complexos com profundidade técnica, talvez o caminho de especialista seja o mais natural, e é totalmente possível crescer nessa trilha sem ir para a gestão. Se, por outro lado, você se interessa por liderar pessoas, estruturar times e facilitar o crescimento de outros, a trilha de gestão pode ser muito recompensadora. Deixando claro, que até mesmo um especialista, precisará ensinar e ser referência para outras pessoas e áreas.</p>
<p>Já a questão de empreender, exige um perfil diferente: é preciso ter visão de longo prazo, capacidade de lidar com incertezas, adaptação constante, apetite a risco e resiliência para errar e aprender rápido. Se você sente prazer em transformar problemas reais com tecnologia, não tem medo de sair da zona de conforto e quer gerar impacto direto, empreender pode ser um caminho transformador.</p>
<p>Minha dica é: teste, se observe e não tenha medo de mudar de rota (erre rápido e mude rápido). Já vi excelentes devs virarem grandes líderes, líderes voltarem a se especializar e até mesmo especialistas fundarem empresas de sucesso. O mais importante é ser intencional com seus passos.</p>
<h2 id="você-lembra-de-algum-conselho-ou-dica-que-recebeu-quando-entrou-nesta-posição-e-que-foi-importante-para-você">Você lembra de algum conselho ou dica que recebeu quando entrou nesta posição e que foi importante para você?</h2>
<p>Venho de uma <strong>cultura afro-indígena</strong> e, desde muito cedo, aprendi a ser meu próprio líder e a valorizar quem trilhou caminhos antes de mim. Isso moldou minha visão sobre liderança e me ajudou a entender que meu foco natural sempre foi a parte técnica e estratégica.</p>
<p>Ao assumir a cadeira de CTO, tomei uma decisão importante: treinei uma pessoa sênior com perfil de governança para assumir a liderança direta do time, cuidando da gestão de pessoas, carreira e cultura. Isso me permitiu atuar com mais profundidade técnica e arquitetural, onde realmente gero valor.</p>
<p>Um conselho que me marcou, e ainda guia minha trajetória, foi:</p>
<blockquote>
<p>&ldquo;Você não vai escalar se tentar ser o melhor em tudo. Foque em criar contextos para que outras pessoas brilhem.&rdquo;</p>
</blockquote>
<p>Essa frase consolidou minha transição de desenvolvedor para uma cadeira executiva com consciência e propósito.</p>
<h2 id="quais-fontes-você-usa-para-se-especializar-blogs-livros-canais-do-youtube">Quais fontes você usa para se especializar? Blogs, livros, canais do Youtube.</h2>
<p>Comecei a estudar computação em 2008 e programar em 2010, aos 14 anos. Desde então, desenvolvi um perfil autodidata forte. Me aprofundo por meio de blogs técnicos (como Medium, Towards Data Science, dev.to e ByteByteGo), papers acadêmicos, documentação oficial e livros técnicos, especialmente nas áreas de arquitetura, dados e IA. Também acompanho canais no YouTube focados em engenharia de dados e exatas (matemática/estatística). Além disso, valorizo muito trocar experiências com profissionais da área, e busco ativamente conversas para benchmarking e aprendizado contínuo.</p>
<h2 id="onde-as-pessoas-podem-te-encontrar-site-linkedin-twitter-etc">Onde as pessoas podem te encontrar? Site, Linkedin, Twitter, etc.</h2>
<p>Sou acessível e ativo pelo LinkedIn, e também mantenho um blog técnico onde compartilho experiências com IA, golang, python, arquitetura distribuída e dados.</p>
<p><a href="mailto:leonardo@rifeli.dev">E-mail</a> <a href="https://rifeli.dev/">Blog</a> <a href="https://www.linkedin.com/in/leonardorifeli">LinkedIn</a></p>
<h2 id="veja-a-entrevista-na-newsletter-maisqueseniordev">Veja a entrevista na newsletter: <a href="https://maisquesenior.substack.com/p/leonardo-rifeli">maisquesenior.dev</a></h2>
]]></content:encoded><category>carreira</category><category>gestao</category></item><item><title>Como gerimos nossos clusters Kubernetes na prática</title><link>https://rifeli.dev/blog/2025-07-10-como-gerimos-nossos-cluters-kubernets-na-pratica/</link><pubDate>Thu, 10 Jul 2025 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/2025-07-10-como-gerimos-nossos-cluters-kubernets-na-pratica/</guid><description>Em ambientes modernos de produção, como o nosso na Harmo, gerenciar clusters Kubernetes de forma eficiente e segura é essencial. Utilizamos o Amazon EKS como nossa base de orquestração, e para operações diárias: observabilidade, debug, troubleshooting e deploys, utilizamos principalmente duas ferramentas: Lens e k9s. Neste artigo, explico como estruturamos nosso acesso, como usamos essas ferramentas no dia a dia e quais os benefícios que elas trazem para nosso time técnico.</description><content:encoded><![CDATA[<h2 id="introdução">Introdução</h2>
<p>Em ambientes modernos de produção, como o nosso na Harmo, gerenciar clusters Kubernetes de forma eficiente e segura é essencial. Utilizamos o <strong>Amazon EKS</strong> como nossa base de orquestração, e para operações diárias: observabilidade, debug, troubleshooting e deploys, utilizamos principalmente duas ferramentas: <a href="https://k8slens.dev/">Lens</a> e <a href="https://k9scli.io/">k9s</a>.</p>
<p>Neste artigo, explico como estruturamos nosso acesso, como usamos essas ferramentas no dia a dia e quais os benefícios que elas trazem para nosso time técnico.</p>
<h2 id="visão-geral-da-arquitetura">Visão geral da arquitetura</h2>
<ul>
<li>Clusters gerenciados no Amazon EKS, com múltiplos ambientes (dev, staging, prod);</li>
<li>Acesso via <code>aws eks update-kubeconfig</code> com autenticação via IAM;</li>
<li>Configuração de kubeconfig versionado por ambiente;</li>
<li>Monitoramento integrado via Prometheus e Grafana (externos ao artigo, mas conectados ao Lens via plugins).</li>
</ul>
<h2 id="por-que-usamos-lens-e-k9s">Por que usamos Lens e k9s</h2>
<p>O uso se dá de acordo com a sua necessidade, eu particularmente, prefiro o <code>k9s</code> pela praticidade de não sair do terminal.</p>
<table>
  <thead>
      <tr>
          <th>Ferramenta</th>
          <th>Finalidade Principal</th>
          <th>Quando Usamos</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Lens</strong></td>
          <td>UI para inspeção visual rápida, insights, logs, eventos, port-forward com 1 clique</td>
          <td>Durante investigações e acompanhamento em tempo real</td>
      </tr>
      <tr>
          <td><strong>k9s</strong></td>
          <td>CLI ágil para navegação rápida, deploys e debugging</td>
          <td>No terminal, via SSH, CI/CD ou local para troubleshooting</td>
      </tr>
  </tbody>
</table>
<h2 id="alias-úteis-para-k9s-por-ambiente">Alias úteis para k9s por ambiente</h2>
<p>É possível facilitar o dia a dia com alguns alias no teu <code>bash profile</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#6272a4"># ~/.bashrc ou ~/.zshrc</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">alias</span> k9s-dev<span style="color:#ff79c6">=</span><span style="color:#f1fa8c">&#34;k9s --context harmo-dev&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">alias</span> k9s-prod<span style="color:#ff79c6">=</span><span style="color:#f1fa8c">&#34;k9s --context harmo-prod&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">alias</span> k9s-stg<span style="color:#ff79c6">=</span><span style="color:#f1fa8c">&#34;k9s --context harmo-staging&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># Também pode abrir com namespace já definido:</span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">alias</span> k9s-namespace<span style="color:#ff79c6">=</span><span style="color:#f1fa8c">&#34;k9s --context {context-name} -n {namespace-name}&#34;</span>
</span></span></code></pre></div><p><strong>Exemplo do k9s</strong>:</p>
<p><img src="https://k9scli.io/assets/screens/xray.png" alt="https://k9scli.io/assets/screens/xray.png"></p>
<p><strong>Exemplo do Lens</strong>:</p>
<p><img src="https://docs.k8slens.dev/using-lens/img/overview.png" alt="https://docs.k8slens.dev/using-lens/img/overview.png"></p>
<h2 id="benefícios-reais-no-dia-a-dia">Benefícios reais no dia a dia</h2>
<ul>
<li>Time técnico é mais produtivo: reduz o uso de <code>kubectl</code> manual;</li>
<li>Integração visual com Lens facilita aprendizado para novos devs;</li>
<li>CLI do k9s é absurdamente rápida para alterações pequenas;</li>
<li>Observabilidade aumentada (com plugins como <code>Prometheus</code> no Lens).</li>
</ul>
<h2 id="cuidados-e-boas-práticas">Cuidados e boas práticas</h2>
<ul>
<li>Nunca salve kubeconfigs sensíveis localmente sem proteção;</li>
<li>Mantenha o acesso IAM dos clusters restrito;</li>
<li>Revogue acessos inativos ou não rotacionados;</li>
<li>Versione e audite permissões RBAC com clareza.</li>
</ul>
<h2 id="conclusão">Conclusão</h2>
<p>O uso combinado de <code>Lens e k9s</code> transformou a forma como operamos nossos clusters no EKS. Ganhamos velocidade, confiabilidade e uma visão muito mais clara do ambiente. Recomendo fortemente essas ferramentas, principalmente quando combinadas com boas práticas de segurança e governança nos clusters.</p>
]]></content:encoded><category>eks</category><category>kubernets</category><category>gestao</category></item><item><title>Agendando cronjobs nativos no RDS Aurora PostgreSQL com pg_cron</title><link>https://rifeli.dev/blog/2025-07-01-agendando-cronjobs-nativos-no-rds-aurora-postgresql-com-pgcron/</link><pubDate>Tue, 01 Jul 2025 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/2025-07-01-agendando-cronjobs-nativos-no-rds-aurora-postgresql-com-pgcron/</guid><description>Em muitas aplicações, é comum a necessidade de executar tarefas recorrentes: limpar dados antigos, atualizar tabelas derivadas ou enviar notificações. Em servidores tradicionais, usamos o cron, um agendador de tarefas do sistema operacional, para executar esses processos de forma automática.</description><content:encoded><![CDATA[<h2 id="introdução">Introdução</h2>
<p>As pessoas que gostam do meio tecnológico, acabam esbarrando neste assunto, um conceito que existe antes dos anos 1970 - como deixar agendado a execução de taferas com SQL em minha base de dados?</p>
<p>A ideia aqui, é trazer informações sobre agendamento de tarefas no AWS RDS Aurora Cluster com PostgreSQL.</p>
<p>Em muitas aplicações, é comum a necessidade de executar tarefas recorrentes: limpar dados antigos, atualizar tabelas derivadas ou enviar notificações. Em servidores tradicionais, usamos o cron, um agendador de tarefas do sistema operacional, para executar esses processos de forma automática.</p>
<p>Desde 2021, conseguimos fazer isso de forma nativa, sem precisar criar nenhuma estrutura adicional, como Lambdas, EC2 e/ou EventBridge.</p>
<p>Neste artigo, mostro como ativar o <strong>pg_cron</strong>, criar tarefas agendadas e compartilhar dicas práticas para usar cronjobs diretamente no RDS Aurora PostgreSQL.</p>
<h2 id="o-que-é-cronjob">O que é cronjob</h2>
<p>Um cronjob é uma tarefa que roda automaticamente em horários definidos. É muito usado para tarefas recorrentes como backup, limpeza de dados e/ou geração de relatórios.</p>
<p>Em ambientes Linux, cron é um agendador de processos do sistema. Mas em bancos PostgreSQL com pg_cron, conseguimos usar sintaxe semelhante ao crontab, com agendamento diretamente via comandos SQL, o que possibilita deixarmos queries agendadas, sem a necessidade do uso de máquinas+triggers.</p>
<h2 id="como-funciona">Como funciona</h2>
<p>Desde a versão 12.5 é possível fazer de forma nativa no RDS Aurora PostgreSQL (antes a gente contornava com evento e lambda). Ou seja, é nativo e permite definir jobs que executam comandos SQL com agendamento cron (ex: todo dia às 2h). Um detalhe importante é que no RDS Aurora, ele roda no mesmo nó primário da instância, então não exige infraestrutura adicional.</p>
<h2 id="cenários">Cenários</h2>
<p>O recurso de cron pode ser utilizado em diversos cenários, principalmente com rotinas na base.</p>
<ul>
<li>Limpeza de dados antigos (data retention);</li>
<li>Geração de relatórios periódicos;</li>
<li>Atualização de dados derivativos ou caches (ex REFRESH MATERIALIZED VIEW);</li>
<li>Detecção de anomalias ou padrões incomuns (agendar análise estatística e salvar alertas);</li>
<li>Manutenção preventiva (como REINDEX e VACUUM).</li>
</ul>
<h2 id="como-habilitar">Como habilitar</h2>
<p>Pra usar o recurso cron em seu cluster RDS Aurora PostgreSQL, segue alguns pontos:</p>
<ul>
<li>A versão dele precisa ser 12.5+ (se for menos, tu tem um problemão haha);</li>
<li>Validar se o parâmetro <code>shared_preload_libraries</code> contém <code>pg_cron</code> (via parameter group do banco);</li>
<li>Ativar a extensão, como user principal da base, rode <code>CREATE EXTENSION IF NOT EXISTS pg_cron;</code>;</li>
<li>Criar suas crons e ser feliz \o/.</li>
</ul>
<p>Pra criar as suas crons, é muito simples:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#ff79c6">SELECT</span> cron.schedule(
</span></span><span style="display:flex;"><span>  <span style="color:#f1fa8c">&#39;cleanup_old_data&#39;</span>, <span style="color:#6272a4">-- name
</span></span></span><span style="display:flex;"><span>  <span style="color:#f1fa8c">&#39;0 2 * * *&#39;</span>, <span style="color:#6272a4">-- crontab config (aqui roda 2am)
</span></span></span><span style="display:flex;"><span>  $$<span style="color:#ff79c6">DELETE</span> <span style="color:#ff79c6">FROM</span> logs <span style="color:#ff79c6">WHERE</span> created_at <span style="color:#ff79c6">&lt;</span> NOW() <span style="color:#ff79c6">-</span> <span style="color:#8be9fd;font-style:italic">interval</span> <span style="color:#f1fa8c">&#39;30 days&#39;</span>$$ <span style="color:#6272a4">-- query
</span></span></span><span style="display:flex;"><span>);
</span></span></code></pre></div><p>Pra gerenciar suas crons, basta validar o que tem: <code>SELECT * FROM cron.job;</code> e pra desativar <code>SELECT cron.unschedule(jobid);</code></p>
<h2 id="-alguns-cuidados">🔐 Alguns cuidados</h2>
<p>Como sempre, nem tudo são flores, segue alguns avisos para você ficar alerta:</p>
<ul>
<li>O pg_cron executa jobs com o mesmo privilégio do usuário que os criou;</li>
<li>Jobs falham silenciosamente se houver erro de permissão;</li>
<li>Ele só roda no nó primário, não em réplicas;</li>
<li>Monitore uso de CPU/memória caso agende jobs pesados.</li>
</ul>
<h2 id="conclusão">Conclusão</h2>
<p>A extensão <code>pg_cron</code> transforma o <code>RDS Aurora PostgreSQL</code> em um banco ainda mais poderoso, permitindo automação direta, sem precisar de infraestrutura externa. É ideal para tarefas periódicas como limpeza de dados, atualização de caches e envio de notificações.</p>
<p>Diz aí, tá usando cron nativo e otimizando recurso de infra?</p>
]]></content:encoded><category>cronjob</category><category>aws-rds</category><category>postgresql</category></item><item><title>Markdown Cheat Sheet</title><link>https://rifeli.dev/posts/markdown-cheat-sheet/</link><pubDate>Sat, 27 Aug 2022 09:16:45 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/posts/markdown-cheat-sheet/</guid><description>Markdown is a powerful tool for creating rich text using a plain text editor. This cheatsheet is a quick reference for Markdown syntax.</description><content:encoded><![CDATA[<p>This Markdown cheat sheet <strong>provides</strong> a quick overview of all the Markdown syntax elements. It can’t cover every edge case, so if you need more information about any of these elements, refer to the reference guides for basic <em>syntax</em> and extended syntax.</p>
<h1 id="headings">Headings</h1>
<hr>
<h1 id="heading-1">Heading 1</h1>
<h2 id="heading-2">Heading 2</h2>
<h3 id="heading-3">Heading 3</h3>
<h4 id="heading-4">Heading 4</h4>
<h5 id="heading-5">Heading 5</h5>
<h6 id="heading-6">Heading 6</h6>
<h1 id="emphasis">Emphasis</h1>
<hr>
<p>Emphasis, aka italics, with asterisks or underscores. <strong>Strong emphasis</strong>, aka bold, with asterisks or underscores. Combined emphasis with asterisks and underscores. <del>Strikethrough</del> with two tildes. <strong><em>Bold and nested italic</em></strong>. <strong><em>All bold and italic</em></strong>. <strong><em><em>Bold and italic nested</em></em></strong>.</p>
<h1 id="images">Images</h1>
<hr>





<figure class='mb-5'>
  <img src="/posts/markdown-cheat-sheet/cat.jpg"  alt="Cat"  width="6016" height="4000" />
  
  
    <figcaption 
      class=''
    >
      This is the default position of a figcaption, but it can be centered or at the end.
    </figcaption>
  
</figure>
<h1 id="lists">Lists</h1>
<hr>
<h2 id="ordered">Ordered:</h2>
<ol>
<li>First ordered list item</li>
<li>Another item</li>
<li>Actual numbers don&rsquo;t matter, just that it&rsquo;s a number
<ol>
<li>1st.</li>
<li>2nd.</li>
<li>3rd.</li>
</ol>
</li>
</ol>
<h2 id="unordered">Unordered:</h2>
<ul>
<li>This is a list item
<ul>
<li>This is a nested list item
<ul>
<li>This is a nested list item</li>
</ul>
</li>
<li>This is another list item</li>
</ul>
</li>
<li>This is another list item</li>
</ul>
<h2 id="task">Task:</h2>
<span class="task-list"></span>

<ul>
<li><input checked="" disabled="" type="checkbox"> Write the press release</li>
<li><input disabled="" type="checkbox"> Update the website</li>
<li><input disabled="" type="checkbox"> Contact the media</li>
</ul>
<h1 id="links">Links</h1>
<hr>
<p><a href="https://www.example.com">This is a link</a>.</p>
<p><a href="https://www.example.com" title="Link Title">This link</a> has a title attribute.</p>
<h1 id="tables">Tables</h1>
<hr>
<table>
  <thead>
      <tr>
          <th>Syntax</th>
          <th>Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Header</td>
          <td>Title</td>
      </tr>
      <tr>
          <td>Paragraph</td>
          <td>Text</td>
      </tr>
  </tbody>
</table>
<h1 id="blockquotes">Blockquotes</h1>
<hr>
<blockquote>
<p>Blockquotes are very handy in email to emulate reply text. This line is part of the same quote.</p>
</blockquote>
<p>You can reference a footnote like this.</p>
<blockquote>
<p>All generalizations are false, including this one. — Mark Twain. <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
</blockquote>
<h1 id="code">Code</h1>
<hr>
<p>Inline <code>code</code> has <code>back-ticks around</code> it.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">var</span> s <span style="color:#ff79c6">=</span> <span style="color:#f1fa8c">&#34;JavaScript syntax highlighting&#34;</span>;
</span></span><span style="display:flex;"><span>alert(s);
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>s <span style="color:#ff79c6">=</span> <span style="color:#f1fa8c">&#34;Python syntax highlighting&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">print</span>(s)
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-plain" data-lang="plain"><span style="display:flex;"><span>No language indicated, so no syntax highlighting.
</span></span><span style="display:flex;"><span>But let&#39;s throw in a &lt;b&gt;tag&lt;/b&gt;.
</span></span></code></pre></div><p>You can remove line numbers, change the highlighting theme, and more. See <a href="https://gohugo.io/content-management/syntax-highlighting/">Syntax Highlighting</a> and <a href="https://gohugo.io/getting-started/configuration-markup/#highlight/">Highlight</a>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-c" data-lang="c"><span style="display:flex;"><span><span style="color:#ff79c6">#include</span> <span style="color:#ff79c6">&lt;stdio.h&gt;</span><span style="color:#ff79c6">
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd">int</span> <span style="color:#50fa7b">main</span>()
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#50fa7b">printf</span>(<span style="color:#f1fa8c">&#34;Hello, World!</span><span style="color:#f1fa8c">\n</span><span style="color:#f1fa8c">&#34;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">return</span> <span style="color:#bd93f9">0</span>;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h1 id="alerts">Alerts</h1>
<hr>



<div class="alert-box info">
  
    <h3 class="alert-title">Optional title</h3>
  
  <p class="flex flex-row items-center gap-2">
    
    <svg
      xmlns="http://www.w3.org/2000/svg"
      fill="none"
      viewBox="0 0 24 24"
      stroke-width="1.5"
      stroke="currentColor"
      class="alert-icon"
    >
      <path
        stroke-linecap="round"
        stroke-linejoin="round"
        d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z"
      />
    </svg>
    
    
This is an info alert.

  </p>
</div>




<div class="alert-box warning">
  
    <h3 class="alert-title">Optional title</h3>
  
  <p class="flex flex-row items-center gap-2">
    
    <svg
      xmlns="http://www.w3.org/2000/svg"
      fill="none"
      viewBox="0 0 24 24"
      stroke-width="1.5"
      stroke="currentColor"
      class="alert-icon"
    >
      <path
        stroke-linecap="round"
        stroke-linejoin="round"
        d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"
      />
    </svg>
    
    
This is a warning alert.

  </p>
</div>




<div class="alert-box error">
  
    <h3 class="alert-title">Optional title</h3>
  
  <p class="flex flex-row items-center gap-2">
    
    <svg
      xmlns="http://www.w3.org/2000/svg"
      fill="none"
      viewBox="0 0 24 24"
      stroke-width="1.5"
      stroke="currentColor"
      class="alert-icon"
    >
      <path
        stroke-linecap="round"
        stroke-linejoin="round"
        d="M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z"
      />
    </svg>
    
    
This is an error alert.

  </p>
</div>




<div class="alert-box success">
  
    <h3 class="alert-title">Optional title</h3>
  
  <p class="flex flex-row items-center gap-2">
    
    <svg
      xmlns="http://www.w3.org/2000/svg"
      fill="none"
      viewBox="0 0 24 24"
      stroke-width="1.5"
      stroke="currentColor"
      class="alert-icon"
    >
      <path
        stroke-linecap="round"
        stroke-linejoin="round"
        d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
      />
    </svg>

    
    
This is a success alert.

  </p>
</div>

<h1 id="math">Math</h1>
<hr>
<p>You can use LaTeX-style math with <code>$</code> and <code>$$</code> delimiters. For example, <code>$x^2$</code> renders as $x^2$, and <code>$$\frac{x}{y}$$</code> renders as: $$\frac{x}{y}$$</p>
<p>We can throw this scary-looking equation at you:</p>
<p>$$
\frac{1}{\Bigl(\sqrt{\phi \sqrt{5}}-\phi\Bigr) e^{\frac25 \pi}} = 1+\frac{e^{-2\pi}} {1+\frac{e^{-4\pi}} {1+\frac{e^{-6\pi}} {1+\frac{e^{-8\pi}} {1+\ldots} } } }
$$</p>
<h1 id="horizontal-rules">Horizontal Rules</h1>
<hr>
<p>Three or more&hellip; Hyphens <code>---</code>, Asterisks <code>***</code>, or Underscores <code>___</code>.</p>
<hr>
<hr>
<hr>
<h1 id="miscellaneous">Miscellaneous</h1>
<hr>
<p>Tailwind lets you conditionally apply utility classes in different states using variant modifiers. For example, use <code>hover:scroll-auto</code> to only <del>The world is flat.</del>
apply the scroll-auto utility on hover.</p>
<dl>
<dt>term</dt>
<dd>definition</dd>
<dd>another definition</dd>
</dl>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://www.brainyquote.com/quotes/mark_twain_137872">https://www.brainyquote.com/quotes/mark_twain_137872</a>.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>Insights/Anotações do mês 01/2022</title><link>https://rifeli.dev/newsletter/2022-01-anotacoes/</link><pubDate>Sun, 16 Jan 2022 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/newsletter/2022-01-anotacoes/</guid><description> 📌 *E aiiiii, como esta? Chegou o dia de compartilhar alguns insights/anotações dos conteúdos que lê esse mês, como sempre tive grande dificuldade de escolher esses artigos, espero que eles ajudem você evoluir profissionalmente 🚀* Como o Medium lida com crescimento do time de engenharia? Em 2017 o time de engenharia do Medium começou escrever como crescimento do seu time colocando as pessoas como primeiro plano e em 2019 escreveram um blogpost falando sobre 2 anos pôs publicação do “Engineering growth: framework overview”.</description><content:encoded><![CDATA[<aside>
📌 *E aiiiii, como esta? Chegou o dia de compartilhar alguns insights/anotações dos conteúdos que lê esse mês, como sempre tive grande dificuldade de escolher esses artigos, espero que eles ajudem você evoluir profissionalmente 🚀*
</aside>
<h2 id="como-o-medium-lida-com-crescimento-do-time-de-engenharia">Como o Medium lida com crescimento do time de engenharia?</h2>
<p><em>Em 2017 o time de engenharia do Medium começou escrever como crescimento do seu time colocando as pessoas como primeiro plano e em 2019 escreveram um blogpost falando sobre 2 anos pôs publicação do “<a href="https://medium.com/s/engineering-growth-framework/engineering-growth-framework-overview-4e02ab330524">Engineering growth: framework overview</a>”.</em></p>
<p>Conversar sobre crescimento deve ser contínua, assim evitando ser uma surpresa na avaliação.</p>
<p>Foi mencionado um estudo sobre pensando sobre crescimento, <a href="https://www.mindsetworks.com/science/"><em>super recomendar ler</em></a>.</p>
<blockquote>
<p><a href="https://medium.engineering/engineering-growth-at-medium-4935b3234d25">https://medium.engineering/engineering-growth-at-medium-4935b3234d25</a></p>
</blockquote>
<hr>
<h2 id="olhando-para-alteração-organizacional-como-uma-evolução-do-produto">Olhando para alteração organizacional como uma evolução do produto</h2>
<ul>
<li>Os usuários do produto &ldquo;empresa&rdquo; são os funcionários;</li>
<li>Não é da conta empresa o que o colaborador faz fora do trabalho, o valor que era pago foi cancelado e agora é feito pagamento do valor direto para o funcionário fazer o que ele julga ser melhor para ele;</li>
<li>Sem review 360, &ldquo;não faz sentido&rdquo; se temos feedback constante dos líderes e pares;</li>
<li>Posição politica deve ser discutido fora da empresa a empresa é para desenvolver software
<ul>
<li>compressão para dar espaço para todos, será que podemos chamar isso por diversidade?</li>
</ul>
</li>
<li>Empresas com muitos anos passa por mudanças, não foi diferente com a Basecamp.</li>
</ul>
<blockquote>
<p><a href="https://world.hey.com/jason/changes-at-basecamp-7f32afc5">https://world.hey.com/jason/changes-at-basecamp-7f32afc5</a></p>
</blockquote>
<hr>
<h2 id="sem-reuniões-sem-tempo-de-entrega-e-sem-time-em-tempo-integral">Sem reuniões, sem tempo de entrega e sem time em tempo integral</h2>
<p>Blogpost do Sahil falando como é o trabalho na Gumroad e como chegou nesse modelo após quase &ldquo;quebrar&rdquo; a empresa e ter que desligar quase todos do time.</p>
<blockquote>
<p><a href="https://sahillavingia.com/work">https://sahillavingia.com/work</a></p>
</blockquote>
<hr>
<h2 id="eu-não-quero-ser-como-o-google">Eu não quero ser como o Google</h2>
<p><strong>Fernanda Weiden</strong> compartilha sua experiência com gestão, colocando alguns pontos importante na “mesa” do que ela não concorda e outras empresas (e principalmente startup) tenta copiar, mas deveria olhar para o que é positivo e adaptar para sua realidade e não simplesmente copiar.</p>
<blockquote>
<p><a href="https://www.goodtogreat.io/p/i-dont-want-to-be-like-google">https://www.goodtogreat.io/p/i-dont-want-to-be-like-google</a></p>
</blockquote>
]]></content:encoded><category>newsletter</category><category>anotações</category><category>insights</category><category>avelino</category><category>open source</category><category>thiago avelino</category></item><item><title>Insights/Anotações do mês 04/2021</title><link>https://rifeli.dev/newsletter/2021-04-anotacoes/</link><pubDate>Tue, 27 Apr 2021 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/newsletter/2021-04-anotacoes/</guid><description> 📌 *E aiiiii, como esta? Chegou o dia de compartilhar alguns insights/anotações dos conteúdos que lê esse mês, como sempre tive grande dificuldade de escolher esses artigos, espero que eles ajudem você evoluir profissionalmente 🚀* Aquele PR irritantes, como você gerencia seu tempo para fazer revisão de código Jessica Joy Kerr escreveu um excelente blogpost sobre revisões de código, ela linka alguns tweets que acabou me levando a reflexão</description><content:encoded><![CDATA[<aside>
📌 *E aiiiii, como esta? Chegou o dia de compartilhar alguns insights/anotações dos conteúdos que lê esse mês, como sempre tive grande dificuldade de escolher esses artigos, espero que eles ajudem você evoluir profissionalmente 🚀*
</aside>
<h2 id="aquele-pr-irritantes-como-você-gerencia-seu-tempo-para-fazer-revisão-de-código">Aquele PR irritantes, como você gerencia seu tempo para fazer revisão de código</h2>
<p>Jessica Joy Kerr escreveu um excelente blogpost sobre revisões de código, ela linka alguns tweets que acabou me levando a reflexão</p>
<blockquote>
<p>Hot take: in-house development has been influenced too much by the GitHub open source PR driven development process. A process driven by zero trust doesn’t fit well in a team with trust.
— <a href="https://twitter.com/pati_gallardo/status/1373343835330383878">Patricia Aas</a></p>
</blockquote>
<p>Sempre usei a gestão de produto open source como uma referência positiva para meu dia-a-dia, a pergunta que me fiz foi: <em>será que estou até hoje fazendo tudo errado?</em></p>
<p>Leia o artigo da Jessica para entender a provocação dela e como ela propõe resolver o problema de code review.</p>
<p><a href="https://jessitron.com/2021/03/27/those-pesky-pull-request-reviews/">https://jessitron.com/2021/03/27/those-pesky-pull-request-reviews/</a></p>
<hr>
<h2 id="pratica-de-atividade-física-de-alta-intensidade-pôs-covid19">Pratica de atividade física de alta intensidade pôs COVID19</h2>
<p>No começo de março comecei sentir os sintomas do COVID, mas demorei para identificar se realmente era essa doença. Meu primeiro cinto foram dores musculares, com meu volume de treino diário era alto sentia diariamente incomodo muscular, mas quando começou as dores de cabeça tinha quase certeza que estava com - não consigo lembrar quando tive dor de cabeça (antes do COVID).</p>
<p>Antes de pegar essa doença <em>invisível</em> me sentia o atleta super-herói 🦸‍♂️,, hoje sou o cagão com receio de pegar novamente.</p>
<p>Leia o post que fiz no Instagram sobre meu momento pôs COVID <a href="https://www.instagram.com/p/CNc1XdKh4lW/">https://www.instagram.com/p/CNc1XdKh4lW/</a></p>
<hr>
<h2 id="a-busca-pela-formula-de-sucesso">A busca pela &ldquo;formula de sucesso&rdquo;</h2>
<p>É comum o ser-humano buscar atalhos para alcançar um objetivo e isso não é diferente no mundo de engenharia de software.</p>
<p>Jacob (conhecido pela comunidade Django - core commiter do projeto - e Python) escreveu um blogpost que começa trazendo o segredo de um truque de mágica para ilustrar como as pessoas acreditã em &ldquo;atalho/formula de sucesso&rdquo;.</p>
<p>Ele compartilha sobre a experiencia que ele teve em um time que estava travado com milhares de bug report e não conseguia sanar os bugs e sempre chegava mais bugs (o que chamo da corrida dos ratos), descreve como fez para tirar as falhas que tinha o software da frente, ele termina afirmando que <strong>não fez o impossivel</strong> e sim <strong>fez o trabalho chato que ninguem queria fazer.</strong></p>
<p><a href="https://jacobian.org/2021/apr/7/embrace-the-grind/">https://jacobian.org/2021/apr/7/embrace-the-grind/</a></p>
<hr>
<h2 id="você-sabe-a-diferença-entre-urgente-e-importante">Você sabe a diferença entre urgente e importante?</h2>
<p>Tenho habito de estudar sobre produtividade e alta performance profissional, esse mês acabei esbarrando em um artido do Todoist sobre evitar à &ldquo;armadilha da urgência&rdquo; com a famosa estrutura de priorização de Dwight D. Eisenhower - <a href="https://www.eisenhower.me/eisenhower-matrix/">Eisenhower Matrix</a>.</p>
<blockquote>
<p>“Who can define for us with accuracy the difference between the long and short term! Especially whenever our affairs seem to be in crisis, we are almost compelled to give our first attention to the urgent present rather than to the important future.”
– Dwight D. Eisenhower, 1961 address to the Century Association</p>
</blockquote>
<p><a href="https://todoist.com/pt-BR/productivity-methods/eisenhower-matrix">https://todoist.com/pt-BR/productivity-methods/eisenhower-matrix</a></p>
<hr>
<h2 id="pesquisa-mostra-que-seu-cérebro-precisa-descansar---trabalho-remoto-vs-pandemia">Pesquisa mostra que seu cérebro precisa descansar - Trabalho remoto vs pandemia</h2>
<p>Você tem se sentido mais cansado agora do que antes da pandemia? Provavelmente você esta trabalhando remoto e tendo diversas reuniões por dia (vídeo conferencia). A Microsoft fez uma pesquisa com 14 pessoas medindo atividade cerebral dos participando em um dia de trabalho com vídeo chamadas (reuniões) usando capacete EEG e como imaginava o nível de estres dos participantes foi alto, principalmente quando não tem intervalos de pelo menos 10min entre reuniões.</p>
<p>A pesquisa recomenda algumas coisas para minimizar nossa atividade cerebral para fazer durante o dia:</p>
<ul>
<li>Mudar nossa forma de pensar</li>
<li>Meditação e prática de atividade física</li>
<li>Antes de agendar uma reunião veja se é necessária ou se é possível resolver via texto (se for resolver via texto, dê o máximo de contexto para os participantes - escreva texto com o pensando que as pessoas envolvidas não sabe que esta falando)</li>
<li>Fazer pausa constante</li>
<li>Tenha pauta para cada reunião (call, video chamada)</li>
<li>Manter os participantes engajado no assunto - o ser humano perde o foco em longas horas de reunião</li>
</ul>
<p>Veja o conteúdo completo da pesquisa: <a href="https://www.microsoft.com/en-us/worklab/work-trend-index/brain-research">https://www.microsoft.com/en-us/worklab/work-trend-index/brain-research</a></p>
<hr>
<h2 id="devhealthy-episódio-22-110kg-a-menos-rotina-de-treino-triathlon-e-pôs-covid19">DevHealthy Episódio 22: 110KG a menos, rotina de treino, triathlon e pôs COVID19</h2>
<p>Participei do episódio 22 do DevHealthy com a Keit Oliveira e Raphael Fabeni.</p>
<blockquote>
<p>O convidado da vez foi o Thiago Avelino, engenheiro open-source e triatleta. Com uma incrível transformação de 110kg a menos, falamos sobre o mundo do triathlon e como ele planeja sua rotina entre treinos e trabalho, além de relatar como foi ficar internado por conta do COVID.</p>
</blockquote>
<p>Escute o episódio <a href="https://avelino.run/quote/lifestyle/devhealthy-22-podcast-110kg-a-menos-triathlon-pos-covid/">https://avelino.run/quote/lifestyle/devhealthy-22-podcast-110kg-a-menos-triathlon-pos-covid/</a></p>
]]></content:encoded><category>newsletter</category><category>anotações</category><category>insights</category><category>avelino</category><category>open source</category><category>thiago avelino</category></item><item><title>How we use Serverless at Harmo</title><link>https://rifeli.dev/blog/2020-10-08-how-we-use-serverless-at-harmo/</link><pubDate>Thu, 08 Oct 2020 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/2020-10-08-how-we-use-serverless-at-harmo/</guid><description>On July 14, 2020, I participated in the Sem Servidor podcast, led by the late Evandro Pires, where I shared how we built our Serverless architecture at Harmo.</description><content:encoded><![CDATA[<p>On July 14, 2020, I participated in the Sem Servidor podcast, led by the late Evandro Pires, where I shared how we built our Serverless architecture at Harmo.</p>
<p>I hope it brings you some valuable insights.</p>
<p>You can listen to the audio on Anchor.fm: <a href="https://anchor.fm/semservidor/episodes/Episdio-3---Serverless-na-Harmo-egknu5">https://anchor.fm/semservidor/episodes/Episdio-3---Serverless-na-Harmo-egknu5</a></p>
<iframe width="100%" height="450" src="https://www.youtube.com/embed/BFRdibgG8BQ" frameborder="0" allowfullscreen></iframe>
<p id="image-legend">Episódio 3: Serverless na Harmo</p>]]></content:encoded><category>event</category><category>serverless</category><category>distributed</category><category>architecture</category></item><item><title>/uses</title><link>https://rifeli.dev/uses/</link><pubDate>Tue, 17 Mar 2020 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/uses/</guid><description>My current setup</description><content:encoded><![CDATA[<p>Sometimes people ask me what my setup looks like for remote work. I decided to try to keep a list of the hardware and software I’m using.</p>
<p>People usually call such pages <a href="https://uses.tech/">&ldquo;my /uses page&rdquo;</a>.</p>
<h1 id="hardware">Hardware</h1>
<img id="image-custom" src="/images/uses/complete-setup.jpg" alt="my desk, as of December, 2024" />
<p id="image-legend">my desk, as of December, 2024</p>
<ul>
<li><strong>Linux Laptop</strong> DELL G15 with an i7-13650HX 14-Core Processor, with 32GB of RAM, 512GB NVMe, and GeForce RTX 3050 6GB</li>
<li><strong>HomeLab</strong> with an AMD-A8-5600k, with 16GB of ram, and NAS with 5TB</li>
<li><strong>Capture Desktop</strong>: with an XEON-E52620-v3, with 16GB of RAM, and RX-580 8GB</li>
<li><strong>Linux Desktop</strong> with an i9-10850K 20-Core Processor, with 32GB of RAM, 1TB NVMe, and a GeForce RTX 4060 8GB</li>
<li><strong>Smartphone Motorola Edge 50 5G</strong></li>
</ul>
<h2 id="with-power-manager">With Power Manager</h2>
<ul>
<li><strong>3.75 kVA 3000W</strong> 4-Stroke Electric Start Power Generator</li>
<li><strong>Nobreak SMS lite 1200VA</strong></li>
<li><strong>Nobreak SMS NET 4+ 1400VA</strong></li>
</ul>
<h1 id="keyboard-mouse-and-ergonomics">Keyboard, Mouse, and Ergonomics</h1>
<img id="image-custom" src="/images/uses/keyboards.jpg" alt="my keyboards, as of March, 2025" />
<p id="image-legend">my keyboards, as of March, 2025</p>
<ul>
<li><strong>Cavaletti C3 Syncron</strong></li>
<li><strong>Logitech Ergo M575</strong></li>
</ul>
<p>I have many keyboards:</p>
<ul>
<li><strong>Aula F75</strong> <em>My favorite 1</em></li>
<li><strong>8BitDo Retro</strong> <em>My favorite 2</em></li>
<li><strong>Retro Sun Microsystems mid 1996</strong></li>
<li><strong>Logitech MX Keys</strong></li>
<li><strong>Logitech K400</strong></li>
<li><strong>Attack Shark K86</strong></li>
<li><strong>Motospeed CK62 with red switch</strong></li>
<li><strong>Magegee STAR with blue switch</strong></li>
</ul>
<img id="image-custom" src="/images/uses/main-keyboard.jpg" alt="my main keyboard, as of March, 2025" />
<p id="image-legend">my main keyboard, as of March, 2025</p>
<h1 id="sound--video">Sound &amp; Video</h1>
<ul>
<li><strong>Monitor Acer Gamer Nitro 31.5&quot; 240Hz</strong> <em>Main</em></li>
<li><strong>Monitor Bettdow 15.6&quot;</strong></li>
<li><strong>Monitor Samsung T450 24&quot; IPS 75Hz</strong></li>
<li><strong>Webcam Lenovo 300 FHD</strong></li>
<li><strong>Motorola Moto Buds+</strong></li>
<li><strong>HyperX SoloCast Microphone</strong></li>
</ul>
<h1 id="software">Software</h1>
<p>Here are some of the apps I use every day and/or enjoy using:</p>
<ul>
<li><a href="https://omakub.org/">Pop!_OS</a>: My default OS on desktop and laptop</li>
<li><a href="https://gnome-terminator.readthedocs.io/en/latest/">Terminator</a>: Terminal</li>
<li><a href="https://alacritty.org/index.html">Alacritty</a>: Terminal</li>
<li><a href="https://www.waveterm.dev/">Wave</a>: Terminal</li>
<li><a href="https://cursor.com/">Cursor</a>: Main Editor</li>
<li><a href="https://code.visualstudio.com/">VSCode</a>: Main Editor</li>
<li><a href="https://www.notion.so/">Notion</a>: Personal docs</li>
<li><a href="https://clickup.com/">ClickUp</a>: Work app</li>
<li><a href="https://www.jetbrains.com/datagrip/">DataGrip</a>: My database manager</li>
<li><a href="https://k8slens.dev/">Lens</a>: Kubernetes manager</li>
</ul>
<h1 id="retired">Retired</h1>
<ul>
<li><strong><a href="https://omakub.org/">Ubuntu with Omakub</a>: Desktop &amp; laptop OS</strong></li>
<li><strong><a href="https://linuxmint.com/download_lmde.php">LMDE 6 &lsquo;Faye&rsquo;</a>: My default OS on desktop and laptop</strong></li>
<li><strong>Monitor Husky Gaming 27&quot; 240Hz</strong></li>
<li><strong>Monitor AOC Hero 24” 144Hz G2460PF</strong></li>
<li><strong>Monitor LG 25UM58 25” IPS</strong></li>
<li><strong>Magic Keyboard 2</strong></li>
<li><strong>Magic Mouse 1</strong></li>
<li><strong>MacBook Pro 14&quot; M2 (2024)</strong></li>
<li><strong>MacBook Air 13&quot; M1 (2022)</strong></li>
<li><strong>MacBook Pro 13&quot; M1 (2021)</strong></li>
<li><strong>MacBook Pro 13&quot; i5 (2020)</strong></li>
<li><strong>MacBook Pro 13&quot; i5 (2013)</strong></li>
</ul>
<h1 id="changelog">Changelog</h1>
<ul>
<li><strong>2025-08-01</strong> - Change main OS, some descriptions, and add a new keyboard</li>
<li><strong>2025-04-30</strong> - Change main OS and some descriptions</li>
<li><strong>2025-03-19</strong> - Added new power managers</li>
<li><strong>2024-12-01</strong> - Added new Dell G15 as main laptop</li>
<li><strong>2024-06-01</strong> - Added last MacBook to retired list</li>
<li><strong>2023-12-13</strong> - Fixed MacBook specs</li>
</ul>
]]></content:encoded></item><item><title>Sobre</title><link>https://rifeli.dev/about/</link><pubDate>Sat, 07 Mar 2020 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/about/</guid><description>Leonardo Rifeli, cofundador e CTO da Harmo. Mais de uma década construindo produtos, plataformas e infraestrutura. Trabalho com arquitetura, banco de dados, cloud e IA em produção, e atendo consultoria técnica.</description><content:encoded><![CDATA[<img id="image-custom" src="/images/about/me.png" alt="Leonardo Rifeli palestrando na GopherCon LATAM 2024" />
<p id="image-legend">Palestra na GopherCon LATAM 2024</p>
<p>Cofundador e CTO da Harmo. Construo produtos e resolvo os problemas que aparecem quando arquitetura, escala, custo e inteligência artificial deixam de ser assunto de slide.</p>
<h2 id="sobre-mim">Sobre mim</h2>
<p>Sou Leonardo Rifeli, empreendedor, engenheiro de software e cientista de dados. Comecei a programar em 2010, aos 14 anos, e a maior parte do caminho desde então foi em sistemas distribuídos, engenharia de dados e infraestrutura.</p>
<p>Hoje sou cofundador e CTO da <a href="https://harmo.me">Harmo</a>, onde lidero a engenharia da plataforma que processa mais de 10 milhões de pesquisas e 300 mil avaliações públicas por mês, para mais de 60 mil lojas de varejo físico no Brasil. Antes da Harmo, cofundei a Reviewr, incubada no MIDITEC, acelerada pela Darwin Startups e reconhecida como uma das startups mais promissoras de Santa Catarina. <a href="/blog/2026-06-08-seis-anos-harmo-cto-infraestrutura-drive-to-store/">Seis anos depois</a>, o que continua me interessando é o mesmo: pegar problema mal definido e transformar em sistema que aguenta o dia seguinte.</p>
<p>Sou Gopher desde 2017. O que sei sobre banco de dados, custo de cloud e observabilidade vem de operar essas coisas em produção, com plataforma no ar e cliente do outro lado.</p>
<h2 id="o-que-faço">O que faço</h2>
<p>Lidero a engenharia, participo das decisões de arquitetura e continuo colocando a mão na massa quando o problema pede. Na prática isso é desenhar plataforma e pipeline de dados, escolher o que entra e o que fica de fora, definir como a operação vai ser observada antes de ela existir, e acompanhar de perto o que roda na AWS.</p>
<p>Boa parte desse trabalho é prevenção, e prevenção não rende história. O que rende é o aprendizado do caminho, e é isso que eu documento aqui: como <a href="/blog/2026-05-24-crise-performance-aurora-postgresql/">tratar performance de banco no Aurora PostgreSQL quando o problema aparece em produção</a>, como <a href="/blog/2026-06-05-loop-improdutivo-step-functions-6x-aws/">instalar guard rails de custo em orquestração serverless</a>, o que muda de verdade entre <a href="/blog/2026-05-28-irsa-vs-pod-identity-eks/">IRSA e Pod Identity no EKS</a> e como <a href="/blog/2026-06-03-relatorios-custo-aws-cronjob-eks/">encurtar a distância entre uma anomalia de custo acontecer e alguém perceber</a>. Escrevo com número e com a parte que deu errado, porque é o tipo de material que eu queria ter encontrado quando precisei.</p>
<p>Minha visão técnica é pragmática. Simplicidade antes de elegância, decisão que cabe no contexto antes de padrão da moda, e desconfiança de complexidade que ninguém pediu. Não existe código mais rápido que código nenhum.</p>
<h2 id="consultoria">Consultoria</h2>
<p>Atendo um número limitado de projetos de <a href="/consultoria/">consultoria técnica</a>. Costuma ser empresa que precisa achar o gargalo antes de comprar máquina maior, revisar arquitetura antes de reescrever, entender por que a conta de cloud cresce mais rápido que o faturamento, ou estruturar dados e IA sem ficar refém de POC que nunca sai do laboratório.</p>
<p>Também faço mentoria e revisão de arquitetura com times que querem uma leitura externa antes de uma decisão difícil de desfazer. Se for o teu caso, os formatos e o contato estão na <a href="/consultoria/">página de consultoria</a>. Contei o raciocínio por trás dessa frente em <a href="/blog/2025-08-02-por-que-decidi-oferecer-consultoria-tecnica/">por que decidi oferecer consultoria técnica</a>.</p>
<h2 id="tecnologias">Tecnologias</h2>
<p>Go desde 2017, onde me sinto em casa. Python para dados, IA e automação. PostgreSQL e Aurora como banco principal, com o respeito que relacional em escala exige. Na AWS, o dia a dia é EKS, Lambda, Step Functions, SQS, OpenSearch e Airflow no MWAA, com infraestrutura como código e observabilidade tratada como parte do produto.</p>
<p>Ferramenta é meio, não identidade. O que pesa na escolha é qual problema ela deixa pra depois.</p>
<h2 id="fora-do-trabalho">Fora do trabalho</h2>
<p>Moro em Antônio Carlos, no interior de Santa Catarina, num sítio onde a vida é família, filhos, cavalos e horta. Boa parte do meu descanso é trabalho manual, o que provavelmente explica por que meus projetos de casa acabam virando post: <a href="/blog/2025-08-10-do-zero-a-home-server-como-e-por-que-montei-meu-nas-em-zfs/">montei um NAS em ZFS do zero</a> e <a href="/blog/2026-07-29-api-escondida-dvr-intelbras-mhdx-curl/">encontrei a API escondida do DVR das minhas câmeras</a> numa tarde de domingo. O projeto atual é instrumentar uma S10 1995 com painel digital, que em algum momento vira série aqui.</p>
<p>Estudar continua sendo hábito, e às vezes o estudo é que vira post, como quando <a href="/blog/2026-06-19-alpinista-nevoeiro-gradiente-descendente-ia/">reencontrei o Cálculo I dentro do treinamento de IA</a> dez anos depois da prova.</p>
<blockquote>
<p>&ldquo;Bom sonho é sonho vivido.&rdquo; — Thiago Avelino</p>
</blockquote>
]]></content:encoded></item><item><title>Princípios SOLID: LSP e sopa de letrinhas</title><link>https://rifeli.dev/blog/2017-12-30-principios-solid-lsp-e-sopa-de-letrinhas/</link><pubDate>Sat, 30 Dec 2017 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/2017-12-30-principios-solid-lsp-e-sopa-de-letrinhas/</guid><description>Este é o terceiro post de uma série onde abordaremos todos os cinco princípios do SOLID. Neste, falaremos sobre “Liskov substitution principle”, abreviado por LSP, e significa literalmente “Princípio da substituição de Liskov”.</description><content:encoded><![CDATA[<p>Este é o terceiro post de uma série onde abordaremos todos os cinco princípios do SOLID. Neste, falaremos sobre “Liskov substitution principle”, abreviado por LSP, e significa literalmente “Princípio da substituição de Liskov”.</p>
<ul>
<li>O primeiro post foi sobre “Single responsibility principle”, abreviado por SRP, e você pode <a href="http://leonardo.rifeli.tech/development/2017/03/20/principios-solid-srp-e-sopa-de-letrinhas.html">ler aqui</a>.</li>
<li>O segundo post foi sobre “Open closed principle”, abreviado por OCP, e você deve <a href="http://leonardo.rifeli.tech/development/2017/12/05/principios-solid-ocp-e-sopa-de-letrinhas.html">ler aqui</a>.</li>
</ul>
<p>Para começar: falar de SOLID é falar de programação orientada a objetos e design (OOD). Tendo isso em mente, o princípio de substituição de Liskov traz outra perspectiva importante: classes filhas nunca deveriam inflingir as definições de tipo da classe pai.</p>
<h2 id="contexto-histórico"><strong>Contexto histórico</strong></h2>
<p>Este conceito foi apresentado por <a href="https://pt.wikipedia.org/wiki/Barbara_liskov">Barbara Liskov</a> numa conferência em 1987, e depois foi publicado em um artigo científico, com o nome <code>[Family Values: A Behavioral Notion of Subtyping](http://reports-archive.adm.cs.cmu.edu/anon/1999/CMU-CS-99-156.ps)</code>, junto de <a href="https://en.wikipedia.org/wiki/Jeannette_Wing">Jeannette Wing</a>, em 1993. Com a seguinte definição original:</p>
<blockquote>
<p>Se q(x) é uma propriedade demonstrável dos objetos x de tipo T. Então q(y) deve ser verdadeiro para objetos y de tipo S onde S é um subtipo de T.</p>
</blockquote>
<p>E após a publicação do livro <a href="https://www.amazon.com/dp/0135974445/">Agile Software Development, Principles, Patterns, and Practices</a>, está definição ficou conhecida como Princípio de Substituição de Liskov. O que nos leva para a definição de Uncle Bob:</p>
<blockquote>
<p>Subclasses devem ser substituíveis pelas classes base.</p>
</blockquote>
<p>Simples, uma subclasse deve poder sobrescrever os métodos da classe base, de modo com que não quebre suas funcionalidades, do ponto de vista do cliente.</p>
<h2 id="problemas-da-violação-do-lsp"><strong>Problemas da violação do LSP</strong></h2>
<ul>
<li>Geração de problemas na classe cliente (pariticipante que está consumindo outro participante);</li>
<li>Comportamentos inesperados no software por suposições equivocadas;</li>
<li>Quebra de outros princípios.</li>
</ul>
<h2 id="exemplo"><strong>Exemplo</strong></h2>
<p>Seguindo o mesmo padrão do primeiro e segundo post, os exemplos (com exceções de alguns participantes) serão exibidos somente com as assinaturas, para reforçar a ideia que Uncle Bob traz, de que a implementação dos métodos é irrelevante para a análise. Somente com as assinaturas, conseguimos perceber se existe (ou não) a violação do princípio.</p>
<p>Usaremos o clássico exemplo do <code>quadrado</code> e do <code>retângulo</code>.</p>
<h4 id="exemplo-do-quadrado-e-retângulo">Exemplo do quadrado e retângulo</h4>
<p>No participante abaixo, temos a classe <strong><code>Rectangle</code></strong> e ela compõe as propriedades <code>width</code> (largura) e <code>height</code> (altura).</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-php" data-lang="php"><span style="display:flex;"><span><span style="color:#ff79c6">&lt;?</span>php
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">namespace</span> Leonardo\Rifeli\Article; 
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">class</span> <span style="color:#50fa7b">Rectangle</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">private</span> <span style="color:#8be9fd;font-style:italic">$width</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">private</span> <span style="color:#8be9fd;font-style:italic">$height</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">public</span> <span style="color:#ff79c6">function</span> <span style="color:#50fa7b">getWidth</span>() { }
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">public</span> <span style="color:#ff79c6">function</span> <span style="color:#50fa7b">getHeight</span>() { }
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">public</span> <span style="color:#ff79c6">function</span> <span style="color:#50fa7b">setWidth</span>(<span style="color:#8be9fd;font-style:italic">$width</span>) { }
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">public</span> <span style="color:#ff79c6">function</span> <span style="color:#50fa7b">setHeigth</span>(<span style="color:#8be9fd;font-style:italic">$heigth</span>) { }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Abaixo temos a classe <strong><code>RectangleArea</code></strong>, responsável por efetuar o cálculo da área de um retângulo.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-php" data-lang="php"><span style="display:flex;"><span><span style="color:#ff79c6">&lt;?</span>php
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">namespace</span> Leonardo\Rifeli\Article; 
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">use</span> Leonardo\Rifeli\Article\Rectangle;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">class</span> <span style="color:#50fa7b">RectangleArea</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#6272a4">// calc rectangle area: $rectangle-&gt;getWidth() * $rectangle-&gt;getHeight().
</span></span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">public</span> <span style="color:#ff79c6">function</span> <span style="color:#50fa7b">calc</span>(Rectangle <span style="color:#8be9fd;font-style:italic">$rectangle</span>) { }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Até aqui tudo dentro do esperado. Temos dois participantes (<strong><code>Rectangle</code></strong> e <strong><code>RectangleArea</code></strong>) e eles funcionam como esperado, pelo menos por enquanto.</p>
<p>Vamos escrever agora um teste para nossos participantes (neste caso teremos implementação para as coisas não ficarem tão abstratas).</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-php" data-lang="php"><span style="display:flex;"><span><span style="color:#ff79c6">&lt;?</span>php
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">namespace</span> Leonardo\Rifeli\Article\Test; 
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">use</span> Leonardo\Rifeli\Article\Rectangle;
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">use</span> Leonardo\Rifeli\Article\RectangleArea;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">class</span> <span style="color:#50fa7b">TestRectangleArea</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">const</span> WIDTH <span style="color:#ff79c6">=</span> <span style="color:#bd93f9">10</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">const</span> HEIGHT <span style="color:#ff79c6">=</span> <span style="color:#bd93f9">5</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">public</span> <span style="color:#ff79c6">function</span> <span style="color:#50fa7b">testCalc</span>(Rectangle <span style="color:#8be9fd;font-style:italic">$rectangle</span>) 
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#8be9fd;font-style:italic">$rectangle</span><span style="color:#ff79c6">-&gt;</span><span style="color:#50fa7b">setWidth</span>(self<span style="color:#ff79c6">::</span><span style="color:#50fa7b">WIDTH</span>);
</span></span><span style="display:flex;"><span>        <span style="color:#8be9fd;font-style:italic">$rectangle</span><span style="color:#ff79c6">-&gt;</span><span style="color:#50fa7b">setHeight</span>(self<span style="color:#ff79c6">::</span><span style="color:#50fa7b">HEIGHT</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#8be9fd;font-style:italic">$rectangleArea</span> <span style="color:#ff79c6">=</span> <span style="color:#ff79c6">new</span> RectangleArea();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">if</span>(<span style="color:#8be9fd;font-style:italic">$rectangleArea</span><span style="color:#ff79c6">-&gt;</span><span style="color:#50fa7b">calc</span>(<span style="color:#8be9fd;font-style:italic">$rectangle</span>) <span style="color:#ff79c6">!==</span> (self<span style="color:#ff79c6">::</span><span style="color:#50fa7b">WIDTH</span> <span style="color:#ff79c6">*</span> self<span style="color:#ff79c6">::</span><span style="color:#50fa7b">HEIGHT</span>))
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">throw</span> <span style="color:#ff79c6">new</span> \Exception(<span style="color:#f1fa8c">&#39;Violated LSP.&#39;</span>);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div>]]></content:encoded><category>SOLID</category></item><item><title>Princípios SOLID: OCP e sopa de letrinhas</title><link>https://rifeli.dev/blog/2017-12-06-principios-solid-ocp-e-sopa-de-letrinhas/</link><pubDate>Wed, 06 Dec 2017 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/2017-12-06-principios-solid-ocp-e-sopa-de-letrinhas/</guid><description>Este é o segundo post de uma série onde abordaremos todos os cinco princípios do SOLID. Neste, falaremos sobre “Open closed principle”, abreviado por OCP, e significa literalmente “Princípio aberto-fechado”.</description><content:encoded><![CDATA[<p>Este é o segundo post de uma série onde abordaremos todos os cinco princípios do SOLID. Neste, falaremos sobre “Open closed principle”, abreviado por OCP, e significa literalmente “Princípio aberto-fechado”.</p>
<p>O primeiro post foi sobre “Single responsibility principle”, abreviado por SRP, e você pode <a href="http://leonardo.rifeli.tech/development/2017/03/20/principios-solid-srp-e-sopa-de-letrinhas.html">ler aqui</a>.</p>
<p>Para começar: falar de SOLID é falar de programação orientada a objetos e design (OOD). Tendo isso em mente, o princípio aberto-fechado traz uma perspectiva importante: os participantes precisam ser abertos para extensão e fechadas para modicação.</p>
<h2 id="antes-de-tudo-os-conceitos-solid-estão-atrelados"><strong>Antes de tudo, os conceitos SOLID estão atrelados?</strong></h2>
<p>De maneira ou outra, sim! No primeiro post, discutimos sobre <a href="http://leonardo.rifeli.tech/development/2017/03/20/principios-solid-srp-e-sopa-de-letrinhas.html">SRP</a> onde os participantes devem possuir somente uma razão para mudança; adicionar uma nova feature irá violar tanto SRP como OCP. Ou seja, quanto maior o número de responsabilidade de um participante, maior a probabilidade de violar OCP.</p>
<p>Portanto, um código que segue SRP tende a estar mais próximo de seguir OCP, por consequência.</p>
<h2 id="tá-e-o-que-é-ser-aberto-para-extensão"><strong>Tá! E o que é ser aberto para extensão?</strong></h2>
<p>Após um software estar em produção, há grande probabilidade de sofrer alterações, evoluir, ter novas features, etc. OCP defende que à partir do momento que o software está em produção, os participantes em questão não poderão sofrer modificações, diminuindo a chance de algum bug ser causado.</p>
<p>Aberto para extensão significa que não podemos modificar o participante que já está em produção e sim exterder as suas funcionalidades atuais e implementar as novas features.</p>
<p>Ou seja, o OCP nos força a desenvolver códigos extensíveis, tornando-os escaláveis e não editáveis.</p>
<p>Com isso, é importante ter a definição de herança bem clara. Você pode ler um pouco sobre no artigo <a href="https://leonardo.rifeli.tech/development/2016/08/19/heranca-ou-composicao-qual-utilizar.html">Herança ou Composição</a>.</p>
<h2 id="problemas-da-violação-do-ocp"><strong>Problemas da violação do OCP</strong></h2>
<ul>
<li>Quebrar outros princípios SOLID;</li>
<li>Maior probabilidade de causar bug;</li>
<li>Um código não escalável e provavelmente menos extensível;</li>
<li>Entre outros.</li>
</ul>
<h2 id="exemplos"><strong>Exemplos</strong></h2>
<p>Seguindo o mesmo padrão do primeiro post, os exemplos serão exibidos somente com as assinaturas, para refornçar a ideia que <em>Uncle Bob</em> traz, de que a implementação dos métodos é irrelevante para a análise. Somente com as assinaturas, conseguimos perceber se existe (ou não) a violação do princípio.</p>
<h4 id="exemplo-com-violação">Exemplo com violação</h4>
<p>Observe o exemplo abaixo, onde temos a classe <em><code>Debit</code></em> e ela precisará debitar um determinado valor de um tipo de débito.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-php" data-lang="php"><span style="display:flex;"><span><span style="color:#ff79c6">&lt;?</span>php
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">namespace</span> Leonardo\Rifeli\Article\Business;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">use</span> <span style="color:#ff79c6">namespace</span> Leonardo\Rifeli\Article\Business\DebitType;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">class</span> <span style="color:#50fa7b">Debit</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">public</span> <span style="color:#ff79c6">function</span> <span style="color:#50fa7b">execute</span>(int value, DebitType debitType) { }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">?&gt;</span>
</span></span></code></pre></div><p>Com o exemplo acima, será necessário ter condições para controlar e implementar as regras de negócio dos tipos de débitos. Considerando que tenhamos os tipos: <em><code>Savings</code></em> e <em><code>CheckingAccount</code></em>, teríamos condições para estes dois tipos e caso novos tipos de détibo surgem, violaríamos OCP.</p>
<h4 id="exemplo-sem-violação">Exemplo sem violação</h4>
<p>No exemplo abaixo, a classe <em><code>Debit</code></em> virará uma <em><code>abstract class</code></em>, e os tipos de conta, serão classes derivadas de <em><code>Debit</code></em>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-php" data-lang="php"><span style="display:flex;"><span><span style="color:#ff79c6">&lt;?</span>php
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">namespace</span> Leonardo\Rifeli\Article\Business\Abstract;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">abstract</span> <span style="color:#ff79c6">class</span> <span style="color:#50fa7b">Debit</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">abstract</span> <span style="color:#ff79c6">public</span> <span style="color:#ff79c6">function</span> <span style="color:#50fa7b">execute</span>(int value) { }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">?&gt;</span>
</span></span></code></pre></div><p>Caso novos tipos de débito surgem, basta extender <em><code>Debit</code></em> e executar a transação com as regras de negócio necessárias.</p>
<p>Com isso, a cada novo tipo, teremos novos códigos e não códigos alterados.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-php" data-lang="php"><span style="display:flex;"><span><span style="color:#ff79c6">&lt;?</span>php
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">namespace</span> Leonardo\Rifeli\Article\Business;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">use</span> Leonardo\Rifeli\Article\Business\Abstract\Debit;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">class</span> <span style="color:#50fa7b">CheckingAccount</span> <span style="color:#ff79c6">extends</span> Debit
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">public</span> <span style="color:#ff79c6">function</span> <span style="color:#50fa7b">execute</span>(int value) { }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">?&gt;</span>
</span></span></code></pre></div><p>Para ampliar nossos exemplo, podemos criar uma classe <em><code>SomeDebit</code></em> tendo os métodos <em><code>setDebit</code></em> e <em><code>execute</code></em>, conforme exemplo abaixo.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-php" data-lang="php"><span style="display:flex;"><span><span style="color:#ff79c6">&lt;?</span>php
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">namespace</span> Leonardo\Rifeli\Article\Business;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">use</span> Leonardo\Rifeli\Article\Business\Abstract\Debit;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">class</span> <span style="color:#50fa7b">SomeDebit</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">private</span> <span style="color:#8be9fd;font-style:italic">$debit</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">public</span> <span style="color:#ff79c6">function</span> <span style="color:#50fa7b">setDebit</span>(Debit <span style="color:#8be9fd;font-style:italic">$debit</span>)
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#8be9fd;font-style:italic">$this</span><span style="color:#ff79c6">-&gt;</span><span style="color:#50fa7b">debit</span> <span style="color:#ff79c6">=</span> <span style="color:#8be9fd;font-style:italic">$debit</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">public</span> <span style="color:#ff79c6">function</span> <span style="color:#50fa7b">execute</span>(int value)
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">return</span> <span style="color:#8be9fd;font-style:italic">$this</span><span style="color:#ff79c6">-&gt;</span><span style="color:#50fa7b">debit</span><span style="color:#ff79c6">-&gt;</span><span style="color:#50fa7b">execute</span>(value);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">?&gt;</span>
</span></span></code></pre></div><p>Conforme novos tipos forem surgindo, basta criá-lo extendendo <em><code>Debit</code></em>, e utilizar o <em><code>SameDebit</code></em> para executar. Aqui, no final, estamos aplicando o padrão de projeto <em><code>Strategy</code></em>.</p>
<h2 id="referências"><strong>Referências</strong></h2>
<ul>
<li><a href="https://www.amazon.com/dp/0135974445/">Livro - Agile Software Development, Principles, Patterns, and Practices</a>;</li>
<li><a href="https://www.youtube.com/watch?v=LsA4QRwq58o&amp;list=PLRX4OtWY_G7N518US48x-EZxXt6h0pr3V&amp;index=2">Hangout sobre OOD - Princípio Open Closed</a>;</li>
<li><a href="https://pt.slideshare.net/MayogaX/ood-princpio-openclosed">OOD - Open Closed Principle</a>;</li>
<li><a href="http://www.hanselminutes.com/145/solid-principles-with-uncle-bob-robert-c-martin">SOLID Principles with Uncle Bob - Robert C. Martin</a>;</li>
<li><a href="https://en.wikipedia.org/wiki/Design_Patterns">Design Patterns GoF</a>;</li>
<li><a href="https://code.tutsplus.com/pt/tutorials/solid-part-2-the-openclosed-principle--net-36600">SOLID part 2 - The Open Closed Principle</a>;</li>
<li><a href="https://en.wikipedia.org/wiki/Strategy_pattern">Strategy pattern</a>;</li>
<li><a href="http://br.phptherightway.com/pages/Design-Patterns.html">PHP do jeito certo - Design Patterns</a>;</li>
<li><a href="https://www.casadocodigo.com.br/products/livro-oo-solid">Casa do Código - Orientação a Objetos e SOLID para Ninjas</a>.</li>
</ul>
<h2 id="conclusão"><strong>Conclusão</strong></h2>
<p>OCP reforça que pensar em orientação a objetos é pensar primeiro na abstração e depois na implementação em si. Vale lembrar que softwares OO evoluem por meio de novos códigos, e não por edições.</p>
<p>Podemos continuar as discussões sobre este princípio nos comentários?</p>
<p>Compartilhe seus aprendizados.</p>
]]></content:encoded><category>SOLID</category></item><item><title>Princípios SOLID: SRP e sopa de letrinhas</title><link>https://rifeli.dev/blog/2017-03-25-principios-solid-srp-e-sopa-de-letrinhas/</link><pubDate>Sat, 25 Mar 2017 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/2017-03-25-principios-solid-srp-e-sopa-de-letrinhas/</guid><description>Em programação de software a sigla SOLID tem ganhado cada vez mais importância. Trata-se de um acrônimo popularizado por Robert C. Martin (o Uncle Bob), onde cada letra representa um dos cinco princípios do OOD (object-oriented design) que, quando aplicados em conjunto ou isoladamente, possibilitam a criação de códigos com facilidade de manter e de se estender ao longo do tempo.</description><content:encoded><![CDATA[<p>Em programação de software a sigla <strong>SOLID</strong> tem ganhado cada vez mais importância. Trata-se de um acrônimo popularizado por <strong>Robert C. Martin</strong> (o Uncle Bob), onde cada letra representa um dos cinco princípios do OOD (object-oriented design) que, quando aplicados em conjunto ou isoladamente, possibilitam a criação de códigos com facilidade de manter e de se estender ao longo do tempo.</p>
<p>Esse é o primeiro post de uma série onde abordaremos todos os cinco princípios do <strong>SOLID</strong>. O primeiro é sobre “Single responsibility principle”, abreviado por <strong>SRP</strong>, e significa literalmente “Princípio da Responsabilidade Única”.</p>
<p>Para começar: falar de SOLID é falar de programação orientada a objetos e design (OOD). Tendo isso em mente, o Princípio de Responsabilidade única traz uma perspectiva diferente para a orientação a objeto: a <strong>coesão</strong>.</p>
<h2 id="tá-e-o-que-é-coesão"><strong>Tá, e o que é coesão?</strong></h2>
<p>Segundo o dicionário online <a href="https://www.dicio.com.br/coesao/">Dicio</a>:</p>
<blockquote>
<p>Cujas partes estão ligadas harmonicamente entre si: coesão do governo. União; harmonia; associação íntima: a coesão das partes de um Estado.Uso correto dos aspectos gramaticais que conectam os elementos de um texto, tornando-o claro e compreensível.[Figurado] Coerência de pensamento; fundamento que dá sentido a uma obra. Aderência; força que une as moléculas e/ou átomos às partes constituintes de um corpo, fazendo com que eles não se partam. (Etm. do francês: cohésion)</p>
</blockquote>
<p>Fonte: <a href="https://www.dicio.com.br/coesao/">dicio.com.br/coesao</a>.</p>
<p>E no mundo do desenvolvimento de software, o que é coesão?</p>
<p>Algo que faça sentido para alguém. E este alguém, é quem irá consumir uma determinada classe e seus métodos. Cada participante deve ter somente um propósito para existir. Ou seja, coesão é consequência de ter-se um bom design e não violar SRP.</p>
<p>E as vantagens de se ter alta coesão (ou “coesão forte”)?</p>
<p>Redução da complexidade das classes e métodos (eles ficam mais simples, com menos operações).</p>
<h2 id="definição-de-responsabilidade"><strong>Definição de responsabilidade</strong></h2>
<p>Segundo o dicionário online <a href="https://www.dicio.com.br/responsabilidade/">Dicio</a>:</p>
<blockquote>
<p>Obrigação; dever de arcar, de se responsabilizar pelo próprio comportamento ou pelas ações de outra(s) pessoa(s).[Por Extensão] Sensatez; competência para se comportar de maneira sensata.Natureza ou condição de responsável; capacidade de responder por seus próprios atos; qualidade de quem presta contas as autoridades.[Jurídico] Obrigação jurídica que resulta do desrespeito de algum direito, através de uma ação contrária ao ordenamento jurídico.</p>
</blockquote>
<p>Fonte: <a href="https://www.dicio.com.br/responsabilidade/">dicio.com.br/responsabilidade</a>.</p>
<p>E no contexto de um código?</p>
<p><strong>Robert C. Martin</strong>, em seu livro (Agile Software Development, Principles, Patterns, and Practices), define responsabilidade como: <strong>uma classe deve ter apenas uma razão para ser alterada</strong>.</p>
<h2 id="problemas-da-violação-do-srp"><strong>Problemas da violação do SRP</strong></h2>
<p>Se uma classe possui mais que uma razão para ser alterada, entende-se que ela possui mais que uma responsabilidade, tornando-a desconexa (não coesa).</p>
<h4 id="quais-problemas-uma-classe-desconexa-poderá-causar-para-a-aplicação">Quais problemas uma classe desconexa poderá causar para a aplicação?</h4>
<ul>
<li>Dificuldade no reuso de suas responsabilidades;</li>
<li>Dificuldades na manutenção (dificuldade em manter e/ou evoluir por conta do excesso de responsabilidades);</li>
<li>Aumento na rigidez e fragilidade: quando alterar uma responsabilidade, outra pode ser comprometida;</li>
<li>Alto acoplamento da classe.</li>
</ul>
<h2 id="exemplos"><strong>Exemplos</strong></h2>
<p>Os códigos dos exemplos serão exibidos somente com as assinaturas, para reforçar a idéia que <strong>Uncle Bob</strong> traz, de que a implementação dos métodos é irrelevante para a análise. Somente com as assinaturas, conseguimos perceber se existe (ou não) a violação do princípio.</p>
<h4 id="exemplo-1">Exemplo 1</h4>
<p>Considere o arquivo abaixo, onde temos a classe <strong><code>PopulationStandardDeviation</code></strong> e a sua responsabilidade é calcular o desvio padrão populacional.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#ff79c6">package</span> com.leonardorifeli.article;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">public</span> <span style="color:#8be9fd;font-style:italic">class</span> <span style="color:#50fa7b">PopulationStandardDeviation</span> {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">public</span> <span style="color:#8be9fd">double</span> <span style="color:#50fa7b">mean</span>() { }
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">public</span> <span style="color:#8be9fd">double</span> <span style="color:#50fa7b">calculate</span>() { }
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">public</span> <span style="color:#8be9fd">double</span> <span style="color:#50fa7b">deviationSumSquare</span>() { }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Perceba que, o nome da classe diz exatamente qual é a sua responsabilidade, calcular o desvio padrão populacional.</p>
<p>Com o exemplo acima, podemos ver rapidamente a violação do princípio, onde ela expõem o método <strong><code>mean()</code></strong> e quem implementa esta classe não espera que ela faça cálculo da média. Apesar da média fazer parte do algoritmo para calcular o <strong>desvio padrão populacional</strong>, ela não faz parte da responsabilidade da classe, logo, a exposição do método <strong><code>mean()</code></strong> mesmo fazendo parte do algoritmo, viola o princípio. O método <strong><code>mean()</code></strong> não deveria ser exposto. Mesmo problema com o método <strong><code>deviationSumSquare()</code></strong>.</p>
<p>Neste caso, para que não haja a violação do SRP, deve-se deixar ambos os métodos (<strong><code>mean()</code></strong> e <strong><code>deviationSumSquare()</code></strong>) como <strong><code>private</code></strong> ou isolar eles em outras classes, injetando-as como dependência na <strong><code>PopulationStandardDeviation</code></strong>.</p>
<h4 id="exemplo-2">Exemplo 2</h4>
<p>Neste segundo exemplo, considere o arquivo abaixo, onde temos a classe <strong><code>Report</code></strong> e a sua responsabilidade é gerar relatório.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#ff79c6">package</span> com.leonardorifeli.article;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">public</span> <span style="color:#8be9fd;font-style:italic">class</span> <span style="color:#50fa7b">Report</span> {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">public</span> ArrayList<span style="color:#ff79c6">&lt;</span>String<span style="color:#ff79c6">&gt;</span> <span style="color:#50fa7b">find</span>() { }
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">public</span> ArrayList<span style="color:#ff79c6">&lt;</span>String<span style="color:#ff79c6">&gt;</span> <span style="color:#50fa7b">proccess</span>() { }
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">public</span> <span style="color:#8be9fd">void</span> <span style="color:#50fa7b">print</span>() { }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>O nome da classe também diz exatamente qual a sua responsabilidade, gerar relatório.</p>
<p>Na visão do usuário, gerar relatório é apenas fazer com que os dados sejam exibidos em tela (ou impressos), de modo organizado. No nível de desenvolvimento de software, gerar relatório engloba vários fatores, sendo eles: buscar os dados, processá-los, organizá-los e exibi-los em tela (ou impressos).</p>
<p>Perceba que para gerar um relatório são envolvidas várias responsabilidades. A classe <strong><code>Report</code></strong>, por exemplo, possui várias razões para ser alterada: como mudar o método <strong><code>find()</code></strong> para buscar os dados em outro lugar, mudar o método <strong><code>proccess()</code></strong> para alterar uma regra de domínio e até mesmo alterar o método <strong><code>print()</code></strong>.</p>
<h4 id="como-poderíamos-melhorar-essa-classe">Como poderíamos melhorar essa classe?</h4>
<p>Inicialmente, precisaríamos isolar o método <strong><code>find()</code></strong> em um contexto de repositório (outra classe que faça somente a busca dos dados no banco). Depois, poderíamos isolar o método <strong><code>proccess()</code></strong> noutra classe e que teria apenas uma responsabilidade, processar os dados que vieram do banco de dados e tratá-los de acordo com o domínio em questão. Finalmente, deixaremos a classe <strong><code>Report</code></strong> com a injeção das suas dependências, tendo somente o método <strong><code>generate()</code></strong>.</p>
<h2 id="referências"><strong>Referências</strong></h2>
<ul>
<li><a href="https://www.amazon.com/dp/0135974445/">Livro - Agile Software Development, Principles, Patterns, and Practices</a>;</li>
<li><a href="https://en.wikipedia.org/wiki/Cohesion_(computer_science)">Article Cohesion - Computer Science</a>;</li>
<li><a href="http://www.hanselminutes.com/145/solid-principles-with-uncle-bob-robert-c-martin">SOLID Principles with Uncle Bob - Robert C. Martin</a>;</li>
<li><a href="https://www.youtube.com/watch?v=dzawoPISdHc">Robert C Martin The Single Responsibility Principle</a>.</li>
</ul>
<h2 id="conclusão"><strong>Conclusão</strong></h2>
<p>O SRP é um dos princípios mais importantes da orientação a objetos. Atentando-se a ele, seus códigos ficarão mais coesos, simples e manuteníveis. É um princípio bem extenso e os exemplos tendem ao infinito.</p>
<p>Podemos continuar as discussões sobre este princípio nos comentários?</p>
<p>Compartilhe conosco seus aprendizados.</p>
<h2 id="agradecimentos"><strong>Agradecimentos</strong></h2>
<p>A ContaAzul, por proporcionar o espaço e me dar a oportunidade de compartilhar meu conhecimento. Ao <strong>Leonardo Camacho</strong>, pelo auxílio nas correções e incentivo para escrever. Para Carlos Becker, Lucas Merencia, Marcos Ferreira, Marcelo Ed. Junior e Jeferson Kersten pelo incentivo e auxílio dos assuntos aqui descritos.</p>
]]></content:encoded><category>SOLID</category></item><item><title>Falando sobre a estrutura do meu blog</title><link>https://rifeli.dev/blog/2016-11-11-falando-sobre-a-estrutura-do-meu-blog/</link><pubDate>Fri, 11 Nov 2016 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/2016-11-11-falando-sobre-a-estrutura-do-meu-blog/</guid><description>Neste artigo, falarei sobre a estrutura do meu blog, um resumo geral de como ele funciona, como faço as publicações, os macetes envolvidos, etc. Recentemente, recebi várias perguntas sobre isso e decidir fazer este artigo para compartilhar isso com você.</description><content:encoded><![CDATA[<p>Neste artigo, falarei sobre a estrutura do meu blog, um resumo geral de como ele funciona, como faço as publicações, os macetes envolvidos, etc. Recentemente, recebi várias perguntas sobre isso e decidir fazer este artigo para compartilhar isso com você.</p>
<h2 id="sumário">Sumário</h2>
<ul>
<li>Introdução;</li>
<li>Resumo da estrutura;</li>
<li>Resumo sobre o Jekyll;</li>
<li>Como usar o Github Pages;</li>
<li>Como configurar seu domínio no Github Pages;</li>
<li>Como automatizar o build;</li>
<li>Como configurar HTTPS (via cloudflare);</li>
<li>Referências;</li>
<li>Conclusão.</li>
</ul>
<h2 id="introdução">Introdução</h2>
<p>Você deve ter percebido que estou tentando manter uma frequência de pelo menos uma publicação por semana, <a href="http://localhost:4000/development/2016/11/05/docker-vamos-falar-sobre-virtualizacao.html">veja aqui o artigo da semana passada</a>.</p>
<p>Antes de entrar neste artigo, certifique-se que você já leu o meu outro artigo, <code>[Porque utilizar o Jekyll](https://leonardorifeli.tech/development/2015/05/06/porque-utilizar-o-jekyll.html)</code>.</p>
<p>Enfim, vamos direto ao ponto. Falarei sobre a estrutura do meu blog. Um resumo geral de como ele funciona, como faço as publicações, os macetes envolvidos, etc. E também, como você pode fazer algo parecido. Recentemente, recebi várias perguntas sobre isso e decidir fazer este artigo para compartilhar isso com você.</p>
<p>Vou escrevendo este magnífico artigo, enquando escuto o set de <strong><a href="https://www.youtube.com/watch?v=tAP9m2XUqjc">Tech House #023 do Mark Jones</a></strong>.</p>
<h2 id="resumo-da-estrutura">Resumo da estrutura</h2>
<p>Nesto ponto, irei dar um breve resumo do que é utilizado no meu blog e nos próximos tópicos, entrarei em mais detalhes.</p>
<p>Basicamente, meu blog é feito em <a href="https://jekyllrb.com/">Jekyll</a> com um template adaptado. Atualmente, ele está hospedado no Github Pages e possuí uma <code>[CDN (Content Delivery Network)](https://pt.wikipedia.org/wiki/Content_Delivery_Network)</code> que neste caso, utilizo o <a href="https://www.cloudflare.com/">Cloud Flare</a>.</p>
<p>Mesmo usando o Github Pages, eu configurei meu domínio <code>leonardorifeli.tech</code> para funcionar com <strong>HTTPs (via Cloud Flare)</strong>. Se você acessar <code>[leonardorifeli.github.io](http://leonardorifeli.github.io/)</code>, você será redirecionado para <code>[leonardorifeli.tech</code> (com HTTPS)](<a href="https://leonardorifeli.tech/)">https://leonardorifeli.tech/)</a>.</p>
<p>E para fazer as publicações? O Jekyll funciona com <a href="https://daringfireball.net/projects/markdown/">Markdown</a>, esse foi o ponto principal para a minha adesão ao Jekyll, com isso, eu escrevo os artigos usando Markdown.</p>
<p>No repositório do meu <a href="https://github.com/leonardorifeli/leonardorifeli.github.io">blog</a> existem duas branchs principais, sendo, <code>gh-pages</code> e <code>master</code>. Eu automatizei o build (utilizando o <a href="https://github.com/leonardorifeli/leonardorifeli.github.io/blob/gh-pages/.travis.yml">travis</a>), com essa automatização, sempre que é efetuado um push para a branch <code>gh-pages</code>, ele irá executar o <strong>bash abaixo</strong> e depois que tudo estiver <strong>OK</strong>, é só fazer um PR (Pull Request) da branch <code>gh-pages</code> para a <code>master</code> e pronto, está no ar.</p>
<p>Salientando que, todos os pushs que eu efetuo, são na branch <code>gh-pages</code> e não (nunca e jamais) na <code>master</code>.</p>
<p><strong>Adicional:</strong> Eu utilizo o Jekyll há mais de um ano (você pode conferir mais sobre o Jekyll no meu artigo <a href="https://leonardorifeli.tech/development/2015/05/06/porque-utilizar-o-jekyll.html">Porque utilizar o Jekyll</a>).</p>
<h2 id="resumo-sobre-o-jekyll">Resumo sobre o Jekyll</h2>
<p><img src="https://jekyllrb.com/img/logo-2x.png" alt="https://jekyllrb.com/img/logo-2x.png"></p>
<p>Jekyll é um gerenciador de códigos estáticos. Isso mesmo, ele não faz uso de banco de dados e não requesita um servidor robusto para funciona. Um dos benefécios é poder utilizar o Github Pages para hospedar o site. Ou seja, você pode desenvolver páginas e até mesmo um blog de forma estática, apenas utilizando HTML (e claro, Markdown) que você provavelmente já conhece. Ele é baseado em vários formatos como Markdown (conforme já dito) para formatação de textos e posts e um padrão de template chamado Liquid com um pouco de <strong>YAML</strong> para os arquivos de configurações.</p>
<p>Você deve ter visto mais no meu artigo sobre, Porque utilizar o Jekyll. Se não, acesse para conhecer mais detalhes sobre este cara. Salientando que, você irá ler a palavra <strong>Jekyll</strong> demasiadas vezes.</p>
<h2 id="como-usar-o-github-pages">Como usar o Github Pages</h2>
<p>Eu expliquei um pouco, no artigo já mencionado, mas, aqui vai um review.</p>
<p>Acesse sua conta no Github, crie um novo repositório com o nome da organização, utilizando o sufixo <code>.github.io</code>. Exemplo: <code>leonardorifeli.github.io</code>.</p>
<p>Em seguido acesse a página do repositório, clique em <code>Settings</code> e depois no box <code>GitHub Pages</code> clique em <code>Automatic page generator</code>, na etapa seguinte clique em <code>Continue to layouts</code>. O próximo passo será selecionar um layout (não se preocupe muito quanto a isso), simplesmente clique em <code>Publish page</code>.</p>
<p>Você poderá encontrar mais detalhes sobre este tópico no site do <a href="https://pages.github.com/">Github Pages</a>.</p>
<p>Após finalizar você poderá clonar o projeto e substituir os arquivos por algum projeto Jekyll já configurado que você tenha encontrado na internet.</p>
<p>Logo após a configuração, você poderá acessar com <code>nome-repositorio.github.io</code>. Porém, acredito que você já tenha um domínio e queira que seu blog seja acessível por ele, exemplo, seudominio.com. Chega mais que é o próximo tópico.</p>
<p>Alguns sites para você encontrar temas para Jekyll:</p>
<ul>
<li><a href="https://github.com/jekyll/jekyll/wiki/Themes">github.com/jekyll/jekyll/wiki/Themes</a>;</li>
<li><a href="https://github.com/planetjekyll/awesome-jekyll-themes">github.com/planetjekyll/awesome-jekyll-themes</a>;</li>
<li><a href="http://jekyllthemes.org/">jekyllthemes.org</a>;</li>
<li><a href="https://jekyllthemes.io/">jekyllthemes.io</a>;</li>
<li><a href="http://jekyll.tips/templates/">jekyll.tips</a>;</li>
<li><a href="https://drjekyllthemes.github.io/">drjekyllthemes.github.io</a>.</li>
</ul>
<p><strong>PS.:</strong> você pode criar Github Pages para qualquer repositório. Eu já vi até documentação do projeto, hospedado no Github Pages (pelo repositório do projeto).</p>
<h2 id="como-configurar-seu-domínio-no-github-pages">Como configurar seu domínio no Github Pages</h2>
<p>Esta etapa, eu considero uma das mais tranquilas. Você precisará apenas, configurar algumas entradas DNS no seu domínio e adicionar um arquivo no repositório.</p>
<p>E como fazer isso?</p>
<p>Saliento que, se você for utilizar uma CDN (detalharei nos próximos tópicos), você precisará alterar novamente a zona de DNS do seu domínio, isso impactará apenas em tempo de propagação do DNS.</p>
<p>Vamos ao que interessa, você precisará acessar as configurações das zonas de DNS do seu domínio e configurar algumas entradas, conforme abaixo:</p>
<ul>
<li>Entrada do tipo A: <code>192.30.252.153</code></li>
<li>Entrada do tipo A: <code>192.30.252.154</code></li>
</ul>
]]></content:encoded><category>Jekyll</category></item><item><title>Docker: Vamos falar sobre virtualização?</title><link>https://rifeli.dev/blog/2016-11-05-docker-vamos-falar-sobre-virtualizacao/</link><pubDate>Sat, 05 Nov 2016 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/2016-11-05-docker-vamos-falar-sobre-virtualizacao/</guid><description>No desenvolvimento de aplicações, podemos optar por usar máquinas virtuais (VMs) para facilitar o gerenciamento e provisionamento de serviços. Para isso, podemos citar o Vagrant. Mas, o provisionamento de máquinas virtuais demanda grande quantidade de tempo, além do fato do consumo demasiado de espaço em disco, recursos em geral da máquina que será o host.</description><content:encoded><![CDATA[<p>No desenvolvimento de aplicações, podemos optar por usar máquinas virtuais (VMs) para facilitar o gerenciamento e provisionamento de serviços. Para isso, podemos citar o Vagrant. Mas, o provisionamento de máquinas virtuais demanda grande quantidade de tempo, além do fato do consumo demasiado de espaço em disco, recursos em geral da máquina que será o host.</p>
<h2 id="sumário">Sumário</h2>
<ul>
<li>Introdução;</li>
<li>Um pouco sobre virtualização;</li>
<li>O que é Docker;</li>
<li>O que é um contêiner;</li>
<li>Namespaces;</li>
<li>Algumas vantagens do Docker;</li>
<li>Principais funcionalidades;</li>
<li>Docker image;</li>
<li>Dockerfile;</li>
<li>Docker compose;</li>
<li>Referências;</li>
<li>Conclusão.</li>
</ul>
<h2 id="introdução">Introdução</h2>
<p>Sim, eu fiquei alguns meses sem escrever! Sorry!</p>
<p>Sem ressentimentos, o assunto deste artigo é muito importante. Falaremos sobre virtualização, isso mesmo. Sim, eu sei que se fôssemos descrevê-lo em muitos detalhes, levaríamos longos e diversos artigos. Portanto, o objetivo deste artigo é trazer um “resumão” sobre este assunto. Falarei sobre <strong>virtualização tradicional</strong>, <strong>virtualização por contêineres</strong> e apresentarei o <strong><a href="https://www.docker.com/">Docker</a></strong> (caso não o conheça).</p>
<p>O objetivo deste artigo é descrever as teorias por volta do tema, farei um segundo artigo, que será um <strong>na prática</strong> com o Docker.</p>
<p>Sem mais delongas, chega mais que vai ser muito foda!</p>
<h2 id="pegue-um-café">Pegue um café</h2>
<p>Corre lá e pegue um pouco de café, o assunto será bem interessante.</p>
<p><img src="https://rifeli.me/img/posts/2016/11/03/get-coffee.gif" alt="https://rifeli.me/img/posts/2016/11/03/get-coffee.gif"></p>
<h2 id="um-pouco-sobre-virtualização">Um pouco sobre Virtualização</h2>
<p>No desenvolvimento de aplicações, podemos optar por usar máquinas virtuais (VMs) para facilitar o gerenciamento e provisionamento de serviços. Para isso, podemos citar o <a href="https://www.vagrantup.com/">Vagrant</a>. Mas, o provisionamento de máquinas virtuais demanda grande quantidade de tempo, além do fato do consumo demasiado de espaço em disco, recursos em geral da máquina que será o host.</p>
<p>Assim surgiu o <a href="https://en.wikipedia.org/wiki/LXC">LXC</a>. O Linux Container, ou LXC, foi lançado em 2008 e é uma tecnologia que permite a criação de múltiplas instâncias isoladas de um determinado sistema operacional. Ou seja, uma maneira de virtualizar aplicações dentro de uma máquina (hospedeira) usando todos os recursos disponíveis no mesmo Kernel da máquina hospedeira.</p>
<p>Tendo como precursor, o comando <a href="https://en.wikipedia.org/wiki/Chroot">chroot</a>, que foi lançado em 1979 pelo <a href="https://en.wikipedia.org/wiki/Version_7_Unix">Unix V7</a>, como intuito de segregar acessos de diretórios e evitar que os usuários possam acessar à estrutura raiz <strong>(/)</strong>. Este conceito evoluiu alguns anos, com o lançamento do comando <a href="https://www.freebsd.org/cgi/man.cgi?query=jail&amp;sektion=8&amp;manpath=freebsd-release-ports">jail</a>, no SO <a href="https://www.freebsd.org/releases/4.0R/announce.html">FreeBSD 4</a>.</p>
<p>Com relação à virtualização, a diferença está no fato do <strong>LXC</strong> não necessitar de uma camada de sistema operacional para cada aplicação. Como você pode verificar na imagem abaixo.</p>
<p><img src="https://rifeli.me/img/posts/2016/11/03/c-structure.png" alt="https://rifeli.me/img/posts/2016/11/03/c-structure.png"></p>
<p>Ao compararmos o <strong>LXC</strong> com a <strong>virtualização tradicional</strong>, fica mais claro que uma aplicação sendo executada em um LXC demanda muito menos recursos, consumindo menos espaço em disco e com um nível de portabilidade muito mais abrangente.</p>
<h2 id="o-que-é-o-docker">O que é o Docker?</h2>
<p><img src="https://rifeli.me/img/posts/2016/11/03/docker.png" alt="https://rifeli.me/img/posts/2016/11/03/docker.png"></p>
<p>Nasceu como um projeto da <a href="https://cloud.docker.com/">DotCloud</a>, uma empresa <strong>PaaS</strong> (Platform as a Service).</p>
<p>Basicamente, Docker é uma plataforma open-source, escrita em <strong>Go</strong>, tendo como finalidade, criar e gerenciar ambientes isolados para aplicações. O Docker garante que, cada contêiner tenha tudo que uma aplicação precisa para ser executada.</p>
<p>Em outras palavras, o Docker é uma ferramenta de empacotamento de uma aplicação e suas dependências em um contêiner virtual que pode ser executado em um servidor linux.</p>
<h2 id="então-docker-é-uma-vm">Então, Docker é uma VM?</h2>
<p>Não, contêineres docker possuem uma arquitetura diferente que permite maior portabilidade e eficiência.</p>
<p><img src="https://rifeli.me/img/posts/2016/11/03/docker-system.png" alt="https://rifeli.me/img/posts/2016/11/03/docker-system.png"></p>
<h2 id="tecnologias-e-ideias-utilizadas">Tecnologias e ideias utilizadas</h2>
<p>Cara, contêiner não é nada novo, Docker surgiu para facilitar o uso deles. Abaixo um resumo de tecnologia e o ano da primeira versão:</p>
<p><img src="https://rifeli.me/img/posts/2016/11/03/technologies-year.png" alt="https://rifeli.me/img/posts/2016/11/03/technologies-year.png"></p>
<h2 id="o-que-é-um-contêiner">O que é um contêiner?</h2>
<p>Vamos fazer uma comparação prática. Contêiner nada mais é que uma caixa de metal, onde é colocado tudo o que couber. Contêineres possuem dimensões e interfaces comuns, onde guindastes e guinchos podem ser acoplados para colocá-los em navios ou caminhões.</p>
<p>Beleza e, no contexto do artigo?</p>
<p>A virtualização em contêineres é muito mais leve, onde, temos cada contêiner como uma instância isolada em um kernel de sistema operacional. Os contêineres possuem interfaces de redes virtuais, processos e sistemas de arquivos independentes.</p>
<p>Algumas características de um contêiner Docker:</p>
<ul>
<li>Dependente de uma imagem (falaremos logo abaixo);</li>
<li>Geram novas imagens;</li>
<li>Conectividade com o host e outros contêineres;</li>
<li>Execuções controladas, CPU, RAM, I/O, etc.</li>
</ul>
<h2 id="namespaces">Namespaces</h2>
<p>O Docker utiliza os recursos de <a href="https://en.wikipedia.org/wiki/Namespace">Namespaces</a> para dispor um espaço de funcionamento isolado para os contêineres. Contudo, quando um contêiner é criado, também é criado um conjunto de namespaces e este, por sua vez, cria uma camada para isolamento para os grupos de processos. Abaixo seguem os tipos de namespaces:</p>
<ul>
<li><strong>PID:</strong> isolamento de processos.</li>
<li><strong>NET:</strong> controle de interfaces de rede.</li>
<li><strong>IPC:</strong> controle dos recursos de IPC (InterProcess Communication).</li>
<li><strong>MNT:</strong> gestão de pontos de montagem.</li>
<li><strong>UTC (Unix Timesharing System):</strong> provém todo o isolamento de recursos do kernel (justamente a camada de abstração como mostra a imagem).</li>
</ul>
<h2 id="algumas-vantagens-do-docker">Algumas Vantagens do Docker</h2>
<ul>
<li>Baixo overhead e tempo de boot;</li>
<li>Kernel compartilhado com o Host;</li>
<li>Contêineres rodam isoladamente;</li>
<li>Facilidade de configuração do ambiente de desenvolvimento para novos membros do time;</li>
<li>Acabar com a história do “na minha máquina funcionava”.</li>
</ul>
<h2 id="principais-funcionalidades">Principais Funcionalidades</h2>
<ul>
<li><strong>Versionamento</strong>: o Docker permite que você versione as alterações de um contêiner. Isto permite verificar as diferenças entre versões, fazer commit de novas versões e até mesmo fazer rollback (isso é muito importante haha).</li>
<li><strong>Compartilhamento de imagens</strong>: sim, existe um repositório de contêineres. O <strong>Docker Hub</strong>. Ele possui milhares de imagens com as mais diversas aplicações. Você pode rapidamente criar sua aplicação com uma base já desenvolvida e ainda criar sua base e compartilhá-la na comunidade.</li>
<li><strong>Licença open-source</strong>: licenciado como <strong>Apache License 2.0</strong>, mantém os códigos fonte disponíveis para facilitar o desenvolvimento colaborativo.</li>
<li><strong>Hardware</strong>: exige poucos recursos de processos, memória e espaço em disco.</li>
<li><strong>Comunicação entre contêineres</strong>: conectar contêineres via mapeamentos de porta <strong>TCP/IP</strong> não é a única forma de disponibilizar recursos entre eles.</li>
</ul>
<p>E uma das principais:</p>
<p><img src="https://rifeli.me/img/posts/2016/11/03/dependency-hell.png" alt="https://rifeli.me/img/posts/2016/11/03/dependency-hell.png"></p>
<ul>
<li><strong>Evita Dependency Hell</strong>: um dos maiores problemas que os desenvolvedores de software convivem, é o gerenciamento de dependências. O Docker evita problemas neste gerenciamento.</li>
</ul>
<h2 id="docker-image">Docker Image</h2>
<p>Uma imagem Docker nada mais é que, um arquivo inerte, imutável, que é essencialmente instanciado por um contêiner. As imagens são criadas com o comando <strong>build</strong> (entrarei em mais detalhes na segunda parte do artigo) e elas serão consumidas por um contêiner, ou seja, um contêiner é a instância de uma imagem. Como as imagens podem ser muito grandes, as imagens são projetadas para serem compostas por camadas de outras imagens.</p>
<p>Basicamente, uma imagem é um conjunto de camadas que você descreve e, quando você inicia uma imagem, você terá um contêiner em execução desta imagem e você pode ter muitos contêineres da mesma imagem. Portanto, uma imagem em execução é um contêiner.</p>
<p>E como criar uma imagem, ou seja, como descrever as camadas de uma imagem? Chega mais…</p>
]]></content:encoded><category>Docker</category></item><item><title>Herança ou composição? Qual utilizar?</title><link>https://rifeli.dev/blog/2016-08-19-heranca-ou-composicao-qual-utilizar/</link><pubDate>Fri, 19 Aug 2016 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/2016-08-19-heranca-ou-composicao-qual-utilizar/</guid><description>Olá dev sapiens, desta vez o artigo será mais teórico (comparado ao último: Web Socket no PHP) e será sobre dois assuntos que possuem demasiada importância no meio da programação orientada a objetos, a herança e a composição.</description><content:encoded><![CDATA[<p>Olá dev sapiens, desta vez o artigo será mais teórico (comparado ao último: <a href="https://leonardorifeli.com/development/2016/05/29/socket-no-php.html">Web Socket no PHP</a>) e será sobre dois assuntos que possuem demasiada importância no meio da programação orientada a objetos, a herança e a composição.</p>
<p>Sim, na internet existem vários artigos sobre o assunto, porém, resolvi descrevê-lo do modo como eu os utilizo.</p>
<p>Enquanto escrevo este magnífico artigo, vou ouvindo um set <strong>Progressive House</strong> do <strong><a href="https://www.youtube.com/watch?v=N7DEv-QP_Zk">Progressive House</a></strong> .</p>
<p>Herança ou composição? E agora, José?</p>
<h2 id="introdução">Introdução</h2>
<p>Um assunto muito abordado e importante na programação orientada a objetos é a utilização de herança ou composição, porém, é visível que muitos programadores(as) optam por utilizar a herança sem mesmo validar as alternativas dentro de cada contexto.</p>
<p>Pois bem, este artigo tem como objetivo colocar os dois assuntos na balança, com o intuito de que você entenda do que cada um é composto e qual utilizar dentro de cada contexto/relação.</p>
<h2 id="função">Função</h2>
<p>A herança e a composição são duas abordagens diferentes para obter-se a reutilização de funcionalidades.</p>
<h2 id="herança">Herança</h2>
<p>Na herança, uma classe herda (daí o termo herança) as propriedades e os métodos de sua classe pai, de modo transitivo, ou seja, uma classe pode herdar de outra classe que herda de outra, até uma classe que não possuí uma classe pai.</p>
<p>Com a herança, as propriedades e os métodos podem se comportar de forma diferente na classe filha, por uso da reescrita dos respectivos métodos.</p>
<p>A herença deverá ser utilizada somente quando existir uma relação <strong>“é-um”</strong> no contexto. No exemplo abaixo, no arquivo <strong>Car.java</strong>, a classe <strong>Car</strong> herda a classe <strong>Automobile</strong> e nesse contexto temos uma relação <strong>“é-um”</strong>, ou seja, <strong>Car</strong> é um <strong>Automobile</strong>, em nenhum momento, <strong>Car</strong> deixará de se comportar como <strong>Automobile</strong>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#ff79c6">package</span> com.leonardorifeli.article.inheritance.model;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">public</span> <span style="color:#8be9fd;font-style:italic">class</span> <span style="color:#50fa7b">Car</span> <span style="color:#8be9fd;font-style:italic">extends</span> Automobile {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">public</span> <span style="color:#50fa7b">Car</span>(<span style="color:#8be9fd;font-style:italic">final</span> String color, <span style="color:#8be9fd;font-style:italic">final</span> Integer quantityPort) {
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">this</span>.<span style="color:#50fa7b">setColor</span>(color);
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">this</span>.<span style="color:#50fa7b">setQuantityPort</span>(quantityPort);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">public</span> String <span style="color:#50fa7b">getColor</span>() {
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">return</span> <span style="color:#f1fa8c">&#34;perfect &#34;</span><span style="color:#ff79c6">+</span> <span style="color:#ff79c6">this</span>.<span style="color:#50fa7b">color</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">public</span> String <span style="color:#50fa7b">myColor</span>() {
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">return</span> <span style="color:#f1fa8c">&#34;Color is: &#34;</span><span style="color:#ff79c6">+</span> <span style="color:#ff79c6">this</span>.<span style="color:#50fa7b">getColor</span>();
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">public</span> String <span style="color:#50fa7b">myQuantityPort</span>() {
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">return</span> <span style="color:#f1fa8c">&#34;Quantity port is: &#34;</span><span style="color:#ff79c6">+</span> <span style="color:#ff79c6">this</span>.<span style="color:#50fa7b">getQuantityPort</span>();
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Verifique que a classe <strong>Car.java</strong> no exemplo acima, está sobrescrevendo o método <strong>getColor()</strong>, alterando o comportamento herdado da classe pai <strong>Automobile.java</strong>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#ff79c6">package</span> com.leonardorifeli.article.inheritance.model;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">public</span> <span style="color:#8be9fd;font-style:italic">class</span> <span style="color:#50fa7b">Automobile</span> {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">private</span> String color;
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">private</span> Integer quantityPort;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">public</span> String <span style="color:#50fa7b">getColor</span>() {
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">return</span> color;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">public</span> <span style="color:#8be9fd">void</span> <span style="color:#50fa7b">setColor</span>(String color) {
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">this</span>.<span style="color:#50fa7b">color</span> <span style="color:#ff79c6">=</span> color;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">public</span> Integer <span style="color:#50fa7b">getQuantityPort</span>() {
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">return</span> quantityPort;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">public</span> <span style="color:#8be9fd">void</span> <span style="color:#50fa7b">setQuantityPort</span>(Integer quantityPort) {
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">this</span>.<span style="color:#50fa7b">quantityPort</span> <span style="color:#ff79c6">=</span> quantityPort;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Por fim, repare que, no contexto exemplificado acima, o ideal é utilizar a herança, pelo fato de ter-se uma relação <strong>“é-um”</strong>, podendo assim, atingir a reutilização dos comportamentos.</p>
<p>Com a evolução, poderíamos ter a classe <strong>Truck.java</strong> que também poderia herdar a classe <strong>Automobile.java</strong>, pelo fato de existir uma relação <strong>“é-um”</strong>, neste outro contexto.</p>
<h2 id="composição">Composição</h2>
<p>Na composição, codificamos pequenos comportamentos, onde uma classe irá apenas instanciar outra classe e utilizar uma propriedade ou um método (claro, os que estão expostos), com isso, podemos usar a composição para comportamentos mais complexos, podendo ainda, alterar a associação entre as classes em tempo de execução da aplicação.</p>
<p>De modo intuitivo, podemos definir a composição como quando uma classe usa um objeto (instância de outra classe) para proporcionar uma parte ou o todo em algum comportamento.</p>
<p>No exemplo abaixo, é utilizado a composição, pelo fato do contexto em questão possuir uma relação <strong>“tem-um”</strong>, onde a classe <strong>Job.java</strong> compoem a classe <strong>People.java</strong>. Neste contexto People pode possuir um Job e iniciá-lo.</p>
<p>Nem sempre uma pessoa irá possuir um emprego, por isso, existe uma relação <strong>“tem-um”</strong>, ou seja, usamos composição e não a herança.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#ff79c6">package</span> com.leonardorifeli.article.composition.model;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">public</span> <span style="color:#8be9fd;font-style:italic">class</span> <span style="color:#50fa7b">People</span> {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">private</span> String name;
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">private</span> <span style="color:#8be9fd">boolean</span> hasJob;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">public</span> <span style="color:#50fa7b">People</span>(<span style="color:#8be9fd;font-style:italic">final</span> String name, <span style="color:#8be9fd;font-style:italic">final</span> <span style="color:#8be9fd">boolean</span> hasJob) {
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">this</span>.<span style="color:#50fa7b">name</span> <span style="color:#ff79c6">=</span> name;
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">this</span>.<span style="color:#50fa7b">hasJob</span> <span style="color:#ff79c6">=</span> hasJob;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">if</span>(<span style="color:#ff79c6">this</span>.<span style="color:#50fa7b">hasJob</span> <span style="color:#ff79c6">==</span> <span style="color:#ff79c6">true</span>) {
</span></span><span style="display:flex;"><span>            Job job <span style="color:#ff79c6">=</span> <span style="color:#ff79c6">new</span> Job(<span style="color:#ff79c6">this</span>.<span style="color:#50fa7b">name</span>, <span style="color:#f1fa8c">&#34;Developer&#34;</span>, <span style="color:#ff79c6">true</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            job.<span style="color:#50fa7b">checkAndStartJob</span>();
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Repare que, o método construtor, instancia a classe <strong>Job.java</strong> somente se <strong>People</strong> possuir um emprego, com isso, podemos acionar o método <strong>checkAndStartJob()</strong> para iniciar o <strong>job</strong>.</p>
<p>Aqui temos uma relação <strong>“tem-um”</strong> e por isso utilizamos a composição. No exemplo acima, com a utilização da composição, podemos alterar a classe em tempo de execução.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#ff79c6">package</span> com.leonardorifeli.article.composition.model;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">public</span> <span style="color:#8be9fd;font-style:italic">class</span> <span style="color:#50fa7b">Job</span> {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">private</span> String name;
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">private</span> String service;
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">private</span> <span style="color:#8be9fd">boolean</span> started <span style="color:#ff79c6">=</span> <span style="color:#ff79c6">false</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">public</span> <span style="color:#50fa7b">Job</span>(<span style="color:#8be9fd;font-style:italic">final</span> String name, <span style="color:#8be9fd;font-style:italic">final</span> String service, <span style="color:#8be9fd;font-style:italic">final</span> <span style="color:#8be9fd">boolean</span> started) {
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">this</span>.<span style="color:#50fa7b">name</span> <span style="color:#ff79c6">=</span> name;
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">this</span>.<span style="color:#50fa7b">service</span> <span style="color:#ff79c6">=</span> service;
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">this</span>.<span style="color:#50fa7b">started</span> <span style="color:#ff79c6">=</span> started;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">public</span> String <span style="color:#50fa7b">checkAndStartJob</span>() {
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">if</span>(<span style="color:#ff79c6">this</span>.<span style="color:#50fa7b">started</span> <span style="color:#ff79c6">==</span> <span style="color:#ff79c6">true</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">return</span> <span style="color:#f1fa8c">&#34;Job has already started.&#34;</span>;
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">if</span>(<span style="color:#ff79c6">this</span>.<span style="color:#50fa7b">started</span> <span style="color:#ff79c6">==</span> <span style="color:#ff79c6">false</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">this</span>.<span style="color:#50fa7b">startJob</span>();
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">return</span> <span style="color:#f1fa8c">&#34;Job is started&#34;</span>;
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">return</span> <span style="color:#f1fa8c">&#34;Job stoped&#34;</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">private</span> <span style="color:#8be9fd">void</span> <span style="color:#50fa7b">startJob</span>() {
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">this</span>.<span style="color:#50fa7b">started</span> <span style="color:#ff79c6">=</span> <span style="color:#ff79c6">true</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">private</span> <span style="color:#8be9fd">void</span> <span style="color:#50fa7b">stopJob</span>() {
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">this</span>.<span style="color:#50fa7b">started</span> <span style="color:#ff79c6">=</span> <span style="color:#ff79c6">false</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Classe <strong>Job.java</strong> e suas propriedades e métodos.</p>
<h2 id="importância">Importância</h2>
<p>A herança e a composição são de extrema importância nas linguagens. Atualmente é raro encontrar linguagens que não as suportem. Caso contrário, seria quase impossível quebrarmos grandes soluções em soluções pequenas/modulares.</p>
<p>Sem a reutilização de comportamentos/funcionalidades não teríamos códigos com responsabilidades únicas, que fazem somente uma coisa e fazem muito bem.</p>
<h2 id="qual-utilizar">Qual utilizar?</h2>
<p>Avalie qual é a relação de um determinado problema. Em caso que exista uma relação <strong>“é-um”</strong> utilizamos a herança, exemplo: banana É uma fruta, carro É um automóvel, pássaro É uma ave etc.</p>
<p>Em casos que a relação tende a funcionalidades e/ou comportamentos específicos e possui uma relação <strong>“tem-um”</strong> , exemplos: pessoa TEM a possibilidade de trabalhar, avião TEM a possibilidade de freiar etc, nestes casos, utilize a composição, aproveitando apenas uma parte (funcionalidade, responsabilidade etc) de outra classe, utilizando o objeto.</p>
<p>Pergunte-se sempre se em todo o ciclo de vida da aplicação ou do código, aquela relação será constante e imutável. Um exemplo de avaliação: Em domínio onde <strong>People</strong> tem relação com <strong>Employee</strong>, neste caso deve-se utilizar a composição, pelo fato de ser algo mutável. Nem sempre <strong>People</strong> terá relação com <strong>Employee</strong>, e se a pessoa ficar desempregada? Portanto, neste caso, o uso da composição é mais adequado do que a herança.</p>
<p>Não use a herança apenas para obter a reutilização de código se não existe uma relação “é-um”. Nestes casos é mais apropriado utilizar a composição.</p>
<h2 id="falando-em-java">Falando em Java</h2>
<p>Apenas para abrir um parêntese no artigo, em Java, toda e qualquer classe possui uma herança, neste caso implicitamente. Toda classe em Java, sempre estenderá Object, com isso, alguns métodos são herdados.</p>
<p>Exemplo de herança com object, sobrescrevendo toString():</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#ff79c6">package</span> com.leonardorifeli.article.inheritance.model;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">public</span> <span style="color:#8be9fd;font-style:italic">class</span> <span style="color:#50fa7b">String</span> {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    @Override
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">public</span> String <span style="color:#50fa7b">toString</span>() {
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">return</span> <span style="color:#f1fa8c">&#34;Is a new string&#34;</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Alguns métodos herdados da classe Object:</p>
<ul>
<li>clone();</li>
<li>equals();</li>
<li>toString();</li>
<li>hashCode();</li>
<li>entre outros.</li>
</ul>
<p>Documentação da classe Object: <a href="http://docs.oracle.com/javase/8/docs/api/java/lang/Object.html">clique aqui</a>.</p>
<h2 id="referências">Referências</h2>
<ul>
<li><a href="https://medium.com/@pedro.barros/heran%C3%A7a-ou-composi%C3%A7%C3%A3o-eis-a-quest%C3%A3o-7ce11fad4737#.ekombw2sy">Post de um amigo, Pedro Augusto</a>;</li>
<li><a href="https://www.thoughtworks.com/pt/insights/blog/composition-vs-inheritance-how-choose">Composition vs. Inheritance: How to Choose?</a>;</li>
</ul>
]]></content:encoded><category>PHP</category></item><item><title>Web Socket no PHP</title><link>https://rifeli.dev/blog/2016-05-30-websocket-no-php/</link><pubDate>Thu, 02 Jun 2016 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/2016-05-30-websocket-no-php/</guid><description>Olá homo sapiens, desta vez o artigo será um pouco mais técnico. Falarei sobre web socket com PHP, claro, também com Javascript.</description><content:encoded><![CDATA[<p>Olá homo sapiens, desta vez o artigo será um pouco mais técnico. Falarei sobre <strong>web socket com PHP</strong>, claro, também com Javascript.</p>
<p>Enquanto escrevo este magnífico artigo, vou ouvindo um <strong>Tech House</strong> do <strong><a href="https://www.youtube.com/watch?v=qhmf1PnLvlw">Trevor Nygaard</a></strong>.</p>
<p>Bom, segue a primeira dica; o artigo será bem extenso, ou seja, corra e pegue uma caneca com muito café (o elixir da vida) e vem comigo que será bem divertido.</p>
<h2 id="introdução">Introdução</h2>
<p>Atualmente a web possuí um tema que é pouco estudado e há poucos artigos e informações na internet. O respectivo tema é <strong>web socket</strong>. Acredito que o tema é pouco falado, devido a sua complexidade. Portanto, não darei uma abordagem profunda neste artigo, será na prática, uma introdução.</p>
<h2 id="objetivo">Objetivo</h2>
<p>Este artigo tem como objetivo, descrever uma breve introducão teórica e prática sobre web socket.</p>
<h2 id="pauta">Pauta</h2>
<p>Neste artigo, acompanharemos a pauta abaixo:</p>
<ol>
<li>suporte dos navegadores;</li>
<li>um breve resumo sobre HTTP;</li>
<li>o que é web socket;</li>
<li>sobre o Ratchet;</li>
<li>exemplo e detalhes;</li>
<li>casos de uso;</li>
<li>conclusão.</li>
</ol>
<h2 id="1-navegadores">1. Navegadores</h2>
<p>Sim, eles devem ser um grande ponto de atenção, não são todos os navegadores que dão suporte a <strong>web socket</strong>, você deve avaliar este ponto antes de qualquer outro. Para isso, o site <a href="http://caniuse.com/#feat=websockets">caniuse</a> informa todos os navegadores bem como suas respectivas versões que possuem suporte a web socket. Como atalho, os navegadores e versões são esboçados na figura abaixo.</p>
<p><img src="https://rifeli.me/img/posts/2016/05/11/support-websocket.png" alt="https://rifeli.me/img/posts/2016/05/11/support-websocket.png"></p>
<p>Como você observou, dependendo do seu público, este tópico não será uma pedra no seu sapato.</p>
<h2 id="2-um-pouco-sobre-http">2. Um pouco sobre HTTP</h2>
<p>Atualmente as requisições HTTP funcionam da seguinte maneira: o navegador abre uma porta de comunicação em um domínio específico, envia uma solicitação de cabeçalho HTTP para o servidor (apache ou nginx), o servidor envia a mensagem para a aplicação, que por sua vez, processa as informações, gera um documento (<strong>HTML</strong>, <strong>JSON</strong>, <strong>XML</strong> etc) e envia o respectivo documento para o servidor. Em seguida, o servidor adiciona os cabeçalhos HTTP apropriados para a requisição, envia de volta para o navegar e encerra a conexão.</p>
<p>Mais informações: <a href="https://pt.wikipedia.org/wiki/Hypertext_Transfer_Protocol#M.C3.A9todos_de_solicita.C3.A7.C3.A3o">Wikipedia</a></p>
<p>E o socket, como funciona? Veja abaixo.</p>
<h2 id="3-o-que-é-web-socket">3. O que é Web Socket?</h2>
<p>Web Sockets são um full-duplex, conexão persistente <strong>bi-direcional</strong> de um navegador web para um servidor. Depois que uma conexão socket é estabelecida a conexão permanece aberta até que o cliente ou servidor decide encerrar. Com esta conexão aberta, o cliente ou servidor pode enviar uma mensagem a qualquer outro cliente conectado. Sendo assim, neste momento, uma única aplicação de servidor em execução estará ciente de todas as conexões abertas, o que lhe permite comunicar com qualquer outra conexão aberta e a qualquer momento.</p>
<p>Adicional:</p>
<blockquote>
<p>Web Socket foi desenvolvido para ser implementado em browsers web e servidores web, mas pode ser usado por qualquer cliente ou aplicação servidor. O protocolo Websocket é um protocolo independente baseado em TCP. Sua única relação com o HTTP é que seu handshake é interpretado por servidores HTTP como uma requisição de upgrade. Fonte: Wikipedia</p>
</blockquote>
<h2 id="4-sobre-o-ratchet">4. Sobre o Ratchet</h2>
<p>As aplicações de socket para servidor não tem acompanhado os navegadores. É aí que surgiu o <strong>Ratchet</strong>, uma ferramenta fantástica para a implementação de um servidor, por protocolo <strong>TCP</strong>. Você pode iniciar um servidor com o <strong>Ratchet I/O Component Server</strong>, tendo um código que implementa o respectivo componente e poderá gerenciar todas as conexões.</p>
<p>Fluxo de uma conexão:</p>
<p><img src="https://rifeli.me//img/posts/2016/05/11/RatchetFlow.png" alt="https://rifeli.me//img/posts/2016/05/11/RatchetFlow.png"></p>
<h2 id="5-ok-show-me-the-code">5. OK, Show me the code!</h2>
<p>Como um amigo (o <a href="https://www.facebook.com/lucasvst?fref=ts">Lucas Teles</a>) sempre fala nos eventos, <strong>show me the code</strong>, apresenta o código cara! No exemplo que irei demonstrar (com base na documentação do Ratchet), iremos seguir as implementações abaixo:</p>
<ul>
<li>dependência do ratchet;</li>
<li>recursos para o servidor de socket;</li>
<li>o gerenciador de conexões;</li>
<li>consumir o web socket, utilizando o lindo Javascript.</li>
</ul>
<h2 id="51-a-dependência">5.1 A Dependência</h2>
<p>Sim, iremos utilizar uma dependência, afinal, quem vive sozinho?</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">&#34;autoload&#34;</span>: {
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">&#34;psr-4&#34;</span>: {
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">&#34;Hermes&#34;</span>: <span style="color:#f1fa8c">&#34;src\\Hermes&#34;</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">&#34;require&#34;</span>: {
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">&#34;cboden/ratchet&#34;</span>: <span style="color:#f1fa8c">&#34;0.3.*&#34;</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Como você pode observar no arquivo <strong>composer.json</strong>, é requerido a dependência**<code>&quot;cboden/ratchet&quot;: &quot;0.3.*&quot;</code>**.</p>
<h2 id="52-implementando-o-servidor">5.2 Implementando o servidor</h2>
<p>Analise o código abaixo, nele é implementado os recursos do Ratchet.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-php" data-lang="php"><span style="display:flex;"><span><span style="color:#ff79c6">&lt;?</span>php
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">require</span> <span style="color:#f1fa8c">&#39;vendor/autoload.php&#39;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">use</span> Ratchet\Server\IoServer;
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">use</span> Ratchet\Http\HttpServer;
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">use</span> Ratchet\WebSocket\WsServer;
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">use</span> Hermes\Business\Service\SocketService;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">$socket</span> <span style="color:#ff79c6">=</span> <span style="color:#ff79c6">new</span> SocketService();
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">$port</span> <span style="color:#ff79c6">=</span> <span style="color:#bd93f9">777</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">$server</span> <span style="color:#ff79c6">=</span> IoServer<span style="color:#ff79c6">::</span><span style="color:#50fa7b">factory</span>(
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">new</span> HttpServer(
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">new</span> WsServer(
</span></span><span style="display:flex;"><span>            <span style="color:#8be9fd;font-style:italic">$socket</span>
</span></span><span style="display:flex;"><span>        )
</span></span><span style="display:flex;"><span>    ),
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">$port</span>
</span></span><span style="display:flex;"><span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">$loop</span> <span style="color:#ff79c6">=</span> <span style="color:#8be9fd;font-style:italic">$server</span><span style="color:#ff79c6">-&gt;</span><span style="color:#50fa7b">loop</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">$server</span><span style="color:#ff79c6">-&gt;</span><span style="color:#50fa7b">run</span>();
</span></span></code></pre></div><p>O que é implementado no arquivo <strong>server.php</strong>, é explicado abaixo:</p>
<ol>
<li><strong>Ratchet\Server\IoServer</strong>: Cria um socket aberto para escutar uma porta específica, para conexões de entrada. Os eventos são delegados através deste para as aplicações anexadas.</li>
<li><strong>Ratchet\Http\HttpServer</strong>: Implementa os métodos da interface <strong>MessageComponentInterface</strong> e gerencia as conexões.</li>
<li><strong>Ratchet\WebSocket\WsServer</strong>: Um adaptador para lidar com as requisições e respostas do web socket. Este é o mediador entre o servidor e o cliente, para lidar com as mensagens em tempo real, por intermédio de um navegador web.</li>
<li><strong>Hermes\Business\Service\SocketService</strong>: Este será nosso gerenciador de conexões, mensagens, erros e encerramentos.</li>
</ol>
<p>Dos itens que serão implementados, só entrarei em detalhes sobre o item quatro, do qual realmente nos interessa. Caso tenha curiosidade, procure como cada item funciona.</p>
<h2 id="53-gerenciador-das-conexões">5.3 Gerenciador das conexões</h2>
<p>Muito bem, o código abaixo, será o gerenciador das conexões, mensagens, encerramentos e erros.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-php" data-lang="php"><span style="display:flex;"><span><span style="color:#ff79c6">&lt;?</span>php
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">namespace</span> Hermes\Business\Service;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">use</span> Ratchet\MessageComponentInterface;
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">use</span> Ratchet\ConnectionInterface;
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">use</span> Hermes\Business\Service\ConnectionInformationService;
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">use</span> Hermes\Business\Service\UserService;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">class</span> <span style="color:#50fa7b">SocketService</span> <span style="color:#ff79c6">implements</span> MessageComponentInterface
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">public</span> <span style="color:#ff79c6">static</span> <span style="color:#8be9fd;font-style:italic">$connections</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">public</span> <span style="color:#ff79c6">function</span> <span style="color:#50fa7b">__construct</span>()
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        self<span style="color:#ff79c6">::</span><span style="color:#8be9fd;font-style:italic">$connections</span> <span style="color:#ff79c6">=</span> <span style="color:#ff79c6">new</span> \SplObjectStorage;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">public</span> <span style="color:#ff79c6">function</span> <span style="color:#50fa7b">onOpen</span>(ConnectionInterface <span style="color:#8be9fd;font-style:italic">$connection</span>)
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#8be9fd;font-style:italic">$queryParams</span> <span style="color:#ff79c6">=</span> ConnectionInformationService<span style="color:#ff79c6">::</span><span style="color:#50fa7b">checkInformations</span>(<span style="color:#8be9fd;font-style:italic">$connection</span>)
</span></span><span style="display:flex;"><span>        
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">if</span>(<span style="color:#ff79c6">!</span><span style="color:#8be9fd;font-style:italic">$queryParams</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#8be9fd;font-style:italic">$connection</span><span style="color:#ff79c6">-&gt;</span><span style="color:#50fa7b">close</span>();
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">return</span>;
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>        <span style="color:#8be9fd;font-style:italic">$user</span> <span style="color:#ff79c6">=</span> UserService<span style="color:#ff79c6">::</span><span style="color:#50fa7b">getNewUser</span>(<span style="color:#8be9fd;font-style:italic">$connection</span>, <span style="color:#8be9fd;font-style:italic">$queryParams</span>);
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>        <span style="color:#8be9fd;font-style:italic">$connection</span><span style="color:#ff79c6">-&gt;</span><span style="color:#50fa7b">session</span> <span style="color:#ff79c6">=</span> <span style="color:#8be9fd;font-style:italic">$user</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        self<span style="color:#ff79c6">::</span><span style="color:#8be9fd;font-style:italic">$connections</span><span style="color:#ff79c6">-&gt;</span><span style="color:#50fa7b">attach</span>(<span style="color:#8be9fd;font-style:italic">$connection</span>);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">public</span> <span style="color:#ff79c6">function</span> <span style="color:#50fa7b">onMessage</span>(ConnectionInterface <span style="color:#8be9fd;font-style:italic">$from</span>, <span style="color:#8be9fd;font-style:italic">$message</span>)
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#8be9fd;font-style:italic">$usersByRoom</span> <span style="color:#ff79c6">=</span> UserService<span style="color:#ff79c6">::</span><span style="color:#50fa7b">getUserByRoom</span>(<span style="color:#8be9fd;font-style:italic">$from</span><span style="color:#ff79c6">-&gt;</span><span style="color:#50fa7b">session</span><span style="color:#ff79c6">-&gt;</span><span style="color:#50fa7b">getRoom</span>(), self<span style="color:#ff79c6">::</span><span style="color:#8be9fd;font-style:italic">$connections</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">foreach</span>(<span style="color:#8be9fd;font-style:italic">$usersByRoom</span> <span style="color:#ff79c6">as</span> <span style="color:#8be9fd;font-style:italic">$user</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#8be9fd;font-style:italic">$user</span><span style="color:#ff79c6">-&gt;</span><span style="color:#50fa7b">send</span>(<span style="color:#f1fa8c">&#34;Message of: </span><span style="color:#f1fa8c">{</span><span style="color:#8be9fd;font-style:italic">$user</span><span style="color:#ff79c6">-&gt;</span><span style="color:#50fa7b">session</span><span style="color:#ff79c6">-&gt;</span><span style="color:#50fa7b">getName</span>()<span style="color:#f1fa8c">}</span><span style="color:#f1fa8c"> - </span><span style="color:#f1fa8c">{</span><span style="color:#8be9fd;font-style:italic">$message</span><span style="color:#f1fa8c">}</span><span style="color:#f1fa8c">&#34;</span>);
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">public</span> <span style="color:#ff79c6">function</span> <span style="color:#50fa7b">onClose</span>(ConnectionInterface <span style="color:#8be9fd;font-style:italic">$connection</span>)
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        self<span style="color:#ff79c6">::</span><span style="color:#8be9fd;font-style:italic">$connections</span><span style="color:#ff79c6">-&gt;</span><span style="color:#50fa7b">detach</span>(<span style="color:#8be9fd;font-style:italic">$connection</span>);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">public</span> <span style="color:#ff79c6">function</span> <span style="color:#50fa7b">onError</span>(ConnectionInterface <span style="color:#8be9fd;font-style:italic">$connection</span>, \Exception <span style="color:#8be9fd;font-style:italic">$e</span>)
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#8be9fd;font-style:italic">$connection</span><span style="color:#ff79c6">-&gt;</span><span style="color:#50fa7b">close</span>();
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Como pode-se observar, a classe <strong>SocketService</strong> implementa a interface <strong>MessageComponentInterface</strong> e possui os métodos: <strong>__construct(), onOpen(), onMessage(), onClose(), onError()</strong>. Bem como, o atributo estático; <strong>$connections</strong>.</p>
<p>Vamos falar sobre a responsabilidade de cada método. Primeiro, o construct; ao iniciar o servidor (explicado no tópico 5.2), o atributo estático $connections, recebe uma instância de <strong>SplObjectStorage</strong> e será responsável por armazenar todas as conexões.</p>
<p><strong>onOpen()</strong>: É o método executado a cada nova conexão, nele você poderá resgatar informações da conexão, como: sala, nome etc, enviadas pelo protocolo GET. O método depende do serviço <strong><a href="http://localhost:4000/development/websocket/php/2016/05/29/socket-no-php.html#connectionInformationService">ConnectionInformationService</a></strong> (exibido abaixo), que será o responsável por tratar as informações recebidas, via GET e retorna em um objeto. Ao receber o objeto, o método envia uma mensagem ao método <strong>getNewUser()</strong> ao serviço <strong>UserService</strong> requisitando uma nova instância de <strong>user</strong> e a adiciona ao storage de objetos.</p>
<h2 id="o-serviço-connection-information">O serviço Connection Information</h2>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-php" data-lang="php"><span style="display:flex;"><span><span style="color:#ff79c6">&lt;?</span>php
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">namespace</span> Hermes\Business\Service;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">use</span> Ratchet\ConnectionInterface;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">abstract</span> <span style="color:#ff79c6">class</span> <span style="color:#50fa7b">ConnectionInformationService</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">static</span> <span style="color:#ff79c6">public</span> <span style="color:#ff79c6">function</span> <span style="color:#50fa7b">checkInformations</span>(ConnectionInterface <span style="color:#8be9fd;font-style:italic">$connection</span>)
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#8be9fd;font-style:italic">$queryExplode</span> <span style="color:#ff79c6">=</span> explode(<span style="color:#f1fa8c">&#39;&amp;&#39;</span>, <span style="color:#8be9fd;font-style:italic">$connection</span><span style="color:#ff79c6">-&gt;</span><span style="color:#50fa7b">WebSocket</span><span style="color:#ff79c6">-&gt;</span><span style="color:#50fa7b">request</span><span style="color:#ff79c6">-&gt;</span><span style="color:#50fa7b">getQuery</span>());
</span></span><span style="display:flex;"><span>        <span style="color:#8be9fd;font-style:italic">$queryParams</span> <span style="color:#ff79c6">=</span> <span style="color:#ff79c6">new</span> \stdClass();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">foreach</span> (<span style="color:#8be9fd;font-style:italic">$queryExplode</span> <span style="color:#ff79c6">as</span> <span style="color:#8be9fd;font-style:italic">$queryParam</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#8be9fd;font-style:italic">$queryParamExplode</span> <span style="color:#ff79c6">=</span> explode(<span style="color:#f1fa8c">&#39;=&#39;</span>, <span style="color:#8be9fd;font-style:italic">$queryParam</span>);
</span></span><span style="display:flex;"><span>            <span style="color:#8be9fd;font-style:italic">$queryParamKey</span> <span style="color:#ff79c6">=</span> <span style="color:#8be9fd;font-style:italic">$queryParamExplode</span>[<span style="color:#bd93f9">0</span>];
</span></span><span style="display:flex;"><span>            array_shift(<span style="color:#8be9fd;font-style:italic">$queryParamExplode</span>);
</span></span><span style="display:flex;"><span>            <span style="color:#8be9fd;font-style:italic">$queryParamValue</span> <span style="color:#ff79c6">=</span> implode(<span style="color:#8be9fd;font-style:italic">$queryParamExplode</span>);
</span></span><span style="display:flex;"><span>            <span style="color:#8be9fd;font-style:italic">$queryParams</span><span style="color:#ff79c6">-&gt;</span><span style="color:#8be9fd;font-style:italic">$queryParamKey</span> <span style="color:#ff79c6">=</span> <span style="color:#8be9fd;font-style:italic">$queryParamValue</span>;
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">if</span> (<span style="color:#ff79c6">!</span>property_exists(<span style="color:#8be9fd;font-style:italic">$queryParams</span>, <span style="color:#f1fa8c">&#39;name&#39;</span>)) <span style="color:#ff79c6">return</span> <span style="color:#ff79c6">false</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">if</span> (<span style="color:#ff79c6">!</span>property_exists(<span style="color:#8be9fd;font-style:italic">$queryParams</span>, <span style="color:#f1fa8c">&#39;room&#39;</span>)) <span style="color:#ff79c6">return</span> <span style="color:#ff79c6">false</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">return</span> <span style="color:#8be9fd;font-style:italic">$queryParams</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>O serviço em questão, verifica os parâmetros enviados por GET e valida se o parâmetro <strong>name</strong> e <strong>room</strong> foram informados.</p>
<p>PS.: O serviço <strong>UserService</strong>, pode ser visualizado <a href="https://gist.github.com/leonardorifeli/037db591223698b96379935a2379f6b7#file-userservice-php">clicando aqui</a>.</p>
<p><strong>onMessage()</strong>: Este método é executado, sempre que, um cliente envia uma mensagem ao servidor, dependendo do domínio da aplicação, a mensagem poderá ser transferida para conexões da mesma sala ou para todas as conexões. No exemplo em questão, a mensagem é transferida para as conexões que estão na mesma sala do remetente.</p>
<p><strong>onClose()</strong>: Sempre que uma conexão for encerrada, o método removerá a conexão do storage de objetos.</p>
<p><strong>onError()</strong>: Este método é para fins bem exclusivos, depende do domínio da aplicação, ele será executado sempre que uma conexão lançar uma exceção. Neste caso, a conexão é finalizada pelo servidor.</p>
<h2 id="consumindo-o-web-socket">Consumindo o web socket.</h2>
<p>Para consumir o servidor de web socket, será utilizado o construtor <strong>WebSocket</strong>. Conforme o exemplo abaixo.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-jsx" data-lang="jsx"><span style="display:flex;"><span>(<span style="color:#8be9fd;font-style:italic">function</span>(){
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    $(<span style="color:#8be9fd;font-style:italic">document</span>).ready(<span style="color:#8be9fd;font-style:italic">function</span>() {
</span></span><span style="display:flex;"><span>        <span style="color:#8be9fd;font-style:italic">var</span> name <span style="color:#ff79c6">=</span> prompt(<span style="color:#f1fa8c">&#34;Qual seu nome?&#34;</span>);
</span></span><span style="display:flex;"><span>        <span style="color:#8be9fd;font-style:italic">var</span> room <span style="color:#ff79c6">=</span> prompt(<span style="color:#f1fa8c">&#34;Qual o nome da sua sala?&#34;</span>);
</span></span><span style="display:flex;"><span>        room <span style="color:#ff79c6">=</span> room.replace(<span style="color:#f1fa8c">/\s/g</span>, <span style="color:#f1fa8c">&#39;&#39;</span>).toLowerCase();
</span></span><span style="display:flex;"><span>        <span style="color:#8be9fd;font-style:italic">var</span> socket <span style="color:#ff79c6">=</span> <span style="color:#f1fa8c">&#34;ws://localhost:777/?&amp;name=&#34;</span><span style="color:#ff79c6">+</span>name<span style="color:#ff79c6">+</span><span style="color:#f1fa8c">&#34;&amp;room=&#34;</span><span style="color:#ff79c6">+</span>room<span style="color:#ff79c6">+</span><span style="color:#f1fa8c">&#34;&#34;</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#8be9fd;font-style:italic">var</span> connect <span style="color:#ff79c6">=</span> <span style="color:#ff79c6">new</span> WebSocket(socket);
</span></span><span style="display:flex;"><span>        <span style="color:#8be9fd;font-style:italic">var</span> users <span style="color:#ff79c6">=</span> [];
</span></span><span style="display:flex;"><span>        
</span></span><span style="display:flex;"><span>        connect.onopen <span style="color:#ff79c6">=</span> <span style="color:#8be9fd;font-style:italic">function</span>(e)
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            connect.send(e.message);
</span></span><span style="display:flex;"><span>        };
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        connect.onmessage <span style="color:#ff79c6">=</span> <span style="color:#8be9fd;font-style:italic">function</span>(e) 
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            connect.send(e.message);
</span></span><span style="display:flex;"><span>        };
</span></span><span style="display:flex;"><span>    });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>})();
</span></span></code></pre></div><p><strong>Repare o ws:</strong> Há um novo esquema de URL para conexões Web Socket. Existe também <strong>wss:</strong> para uma conexão Web Socket é usado para conexões HTTP seguras.</p>
<p>Com isso, você pode manipular mensagens para o servidor e, ele por sua vez, repassar para outras conexões.</p>
<h2 id="casos-de-uso">Casos de uso</h2>
<p>Sempre que precisar de uma conexão quase em tempo real de baixa latência entre o cliente e o servidor, você terá que implementar Web Socket. Isso pode envolver a reformulação do modo como você desenvolve as aplicações de servidor com um novo foco em tecnologias como filas de eventos.</p>
<p>Alguns exemplos de casos de uso:</p>
<ul>
<li>usuários editando um mesmo registro;</li>
<li>chats;</li>
<li>links que precisam de rápida atualização;</li>
<li>jogos on-line de vários players;</li>
<li>atualização em tempo real de redes sociais.</li>
</ul>
<h2 id="referências">Referências</h2>
<ul>
<li><a href="http://www.html5rocks.com/pt/tutorials/websockets/basics/">Apresentando WebSockets: trazendo soquetes para a web</a></li>
<li><a href="https://gist.github.com/leonardorifeli/037db591223698b96379935a2379f6b7">Gist Completo</a></li>
<li><a href="http://socketo.me/docs">Introduction to WebSockets</a></li>
</ul>
<h2 id="conclusão">Conclusão</h2>
<p>Chegamos a um ponto de tecnologias e exigências, onde, as aplicações estão cada vez mais complexas, mais inteligêntes, mais isoladas de acordo com suas responsabilidades. O tema do qual eu escrevi está sendo utilizado cada vez mais nas aplicações que necessitam de atualizações instantâneas de informações.</p>
<p>Vale muito perder um tempo estudando e projetando. Um dia, você desenvolvedor, irá precisar de Web Socket.</p>
<p>Tem algo para incrementar? Utilize os comentários abaixo, agregue valor para a comunidade. Quaisquer críticas construtivas, serão bem-vindas.</p>
]]></content:encoded><category>PHP</category></item><item><title>Porque eu me mudei para Joinville</title><link>https://rifeli.dev/blog/2016-05-29-porque-me-mudei-para-joinville/</link><pubDate>Sun, 29 May 2016 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/2016-05-29-porque-me-mudei-para-joinville/</guid><description>Saudação méro mortal, lembra o post maroto que escrevi há alguns meses? (caso não tenha efetuado uma sábia leitura, clique aqui). Então, nele eu me desculpei e argumentei que estou em uma viagem pela galáxia. Pois bem, este post é para conversarmos sobre essa incrível viagem.</description><content:encoded><![CDATA[<p>Saudação méro mortal, lembra o post maroto que escrevi há alguns meses? (caso não tenha efetuado uma sábia leitura, clique aqui). Então, nele eu me desculpei e argumentei que estou em uma viagem pela galáxia. Pois bem, este post é para conversarmos sobre essa incrível viagem.</p>
<p>Enquanto escrevo, vou ouvindo o set <a href="https://www.youtube.com/watch?v=v0X_tOCY_yQ">Vegas Deep</a> do DJ Paulo Arruda.</p>
<p>Chega de bla, bla, bla e vem comigo.</p>
<h2 id="introdução">Introdução</h2>
<p>Irei descrever os pontos que me levaram a aceitar o convite e como tal decisão impactou em minha carreira como desenvolvedor.</p>
<h2 id="a-origem">A Origem</h2>
<p>A viagem teve início na minha cidade natal, Araraquara (a morada do sol), interior de São Paulo. O convite veio de uma agência web, localizada em Joinville, interior de Santa Catarina, donde, me foi informado que essa viagem não seria fácil, que seria longa, com grandes turbulências e muitos, muitos DESAFIOS.</p>
<p>Sim, foi necessário muitos planejamentos (no caso, a longo prazo) para aceitar o convite e deixar minha cidade natal, famíliares, amigos, por tempo indeterminado.</p>
<h2 id="a-decisão">A Decisão</h2>
<p>A decisão foi tomada com base nas descrições da viagem (longa, grandes turbulências, muitos DESAFIOS etc).</p>
<p>Todos nós cheganos a um ponto de nossas carreiras, que precisamos tomar uma determinada decisão e tal irá modificar por completo nossas vidas profissionais, pessoais e até amorosas, pois bem, a decisão que tomei para aceitar a viagem, impactou em muitas mudanças positivas para várias áreas da minha vida.</p>
<h2 id="a-viagem">A Viagem</h2>
<p>Desde que iniciou-se, tem sido de grande valia para minha vida profissional, de onde tem agregado conhecimentos indispensáveis e uma curva de aprendizado muito ampla.</p>
<p>Atualmente a viagem possuí uma duração de aproximadamente quatro meses e já apliquei muitos conhecimentos adquiridos e aprimorei-os.</p>
<h2 id="pontos-positivos">Pontos positivos</h2>
<ul>
<li>ampla curva de aprendizado;</li>
<li>novas amizades;</li>
<li>evolução pessoal;</li>
<li>adquirir novas experiências e novos conhecimentos;</li>
<li>aplicação e aprimoração de experiências e conhecimentos;</li>
<li>impacto da saída da zona de conforto;</li>
<li>entre muitos.</li>
</ul>
<p>Repare, alguns pontos impactam diretamente em minha vida profissional.</p>
<h2 id="pontos-negativos">Pontos negativos</h2>
<ul>
<li>longe de familiares e amigos;</li>
<li>diversidade cultural e o clima;</li>
<li>hábito com trânsito e pessoas.</li>
</ul>
<p>Repare, os pontos não impactam diretamente em minha vida profissional.</p>
<h2 id="conclusão">Conclusão</h2>
<p>Estou viajando há aproximadamente quatro meses e levando em consideração os meses já viajados, a curva de crescimento vem aumentando.</p>
<p>E então, qual foi a última vez que você fez algo pela primeira vez?</p>
<p>Quer compartilhar algo parecido? Use os comentários abaixo.</p>
<p>Beijos na alma!</p>
]]></content:encoded><category>Vida</category></item><item><title>Implementando annotations com doctrine annotation reader em PHP</title><link>https://rifeli.dev/blog/2016-01-22-implementando-annotations-com-doctrine-annotation-reader-em-php/</link><pubDate>Fri, 22 Jan 2016 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/2016-01-22-implementando-annotations-com-doctrine-annotation-reader-em-php/</guid><description>Saudações méros mortais, primeiro, peço desculpas pelo período sem trazer novidades para vocês (estive em uma excursão pela galáxia), em breve escreverei um artigo sobre essa viagem. Neste artigo mostrarei sobre annotations (é claro, está no título).</description><content:encoded><![CDATA[<p>Saudações méros mortais, primeiro, peço desculpas pelo período sem trazer novidades para vocês (estive em uma excursão pela galáxia), em breve escreverei um artigo sobre essa viagem. Neste artigo mostrarei sobre <strong>annotations</strong> (é claro, está no título).</p>
<p>Como disse meu amigo <a href="https://medium.com/@guidiego">Guilherme Diego</a> no artigo <a href="https://medium.com/@guidiego/c%C3%B3digo-limpo-%C3%A9-uma-responsabilidade-blocos-5be1fdd8d341#.gbx5keq0s">Código Limpo é uma Responsabilidade — Blocos</a>:</p>
<p><img src="https://rifeli.me/img/posts/2016-01-custom-annotations/ler-curtir-compartilhar.png" alt="https://rifeli.me/img/posts/2016-01-custom-annotations/ler-curtir-compartilhar.png"></p>
<p>Enquanto escrevo, vou ouvindo o álbum <a href="https://open.spotify.com/album/6iJEtgHTEbVlSS5isIS71z">As Daylight Dies</a> da banda Killswitch Engage, é um banda muito bacana.</p>
<p>Enfim, vamos ao que interessa, vem comigo.</p>
<h2 id="introdução">Introdução</h2>
<p>Em um projeto recente do qual participei do processo de <strong>refactoring</strong>, o que me auxiliou bastante, foi a implementação de <strong>annotations</strong>, onde foi possível segregar informações estáticas e até atingir algumas práticas de <strong>clean code</strong>, salientando que isso foi uma solução que funcionou bem no respectivo projeto.</p>
<p>Neste artigo eu não discutirei se é o correto, ou não, apenas demonstrarei como implementar <strong>custom annotations</strong> com o <strong>doctrine reader</strong>. Fica sobre teu critério meu chapa!</p>
<h2 id="escopo">Escopo</h2>
<p>No exemplo que mostrarei, utilizaremos os seguintes arquivos:</p>
<ol>
<li><strong>compose.json</strong>: Dependência e informações do projeto;</li>
<li><strong>PeopleAnnotation.php</strong>: Será a nossa annotation, utilizaremos os atributos para receber valores de quem irá consumir a <strong>annotation</strong>;</li>
<li><strong>People.php</strong>: Iremos consumir nossa annotation e informar os respectivos valores para segregarmos informações;</li>
<li><strong>ReaderAnnotation.php</strong>: Neste arquivo iremos juntar tudo e fazer uma sopa de letrinhas.</li>
</ol>
<p>Irei demonstrar os códigos no artigo, caso necessário, você poderá verificar no <a href="https://gist.github.com/leonardorifeli/9c12f94b109cb7859ca9">Gist</a>.</p>
<h2 id="dependência-sim-você-precisará-dela">Dependência, sim você precisará dela.</h2>
<p>Para trabalhar com o <strong>Doctrine Annotation Reader</strong>, será necessário possuir a dependência <strong>“doctrine/common”</strong>, conforme o arquivo <strong>composer.json</strong> abaixo:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">&#34;name&#34;</span>: <span style="color:#f1fa8c">&#34;working-annotation&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">&#34;license&#34;</span>: <span style="color:#f1fa8c">&#34;MIT&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">&#34;type&#34;</span>: <span style="color:#f1fa8c">&#34;project&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">&#34;description&#34;</span>: <span style="color:#f1fa8c">&#34;Using annotations&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">&#34;require&#34;</span>:
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">&#34;doctrine/common&#34;</span>: <span style="color:#f1fa8c">&#34;*&#34;</span>
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">&#34;authors&#34;</span>: [
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">&#34;name&#34;</span>: <span style="color:#f1fa8c">&#34;Leonardo Rifeli&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">&#34;email&#34;</span>: <span style="color:#f1fa8c">&#34;leonardorifeli@gmail.com&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">&#34;homepage&#34;</span>: <span style="color:#f1fa8c">&#34;http://leonardorifeli.com&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">&#34;role&#34;</span>: <span style="color:#f1fa8c">&#34;Back-end Developer&#34;</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    ]
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h2 id="desenvolvendo-a-classe-da-annotation">Desenvolvendo a classe da annotation.</h2>
<p>Resumindo, esta classe será responsável pela <strong>annotation</strong>, ou seja, os atributos <strong>públicos</strong> da classe armazenarão informações que poderão ser informadas por quem irá consumir a <strong>annotation</strong> em questão. Segue abaixo o arquivo <strong>PeopleAnnotation.php</strong>, é a nossa annotation:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-php" data-lang="php"><span style="display:flex;"><span><span style="color:#ff79c6">&lt;?</span>php
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f1fa8c">/**
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">* @Annotation
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">*/</span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">class</span> <span style="color:#50fa7b">PeopleAnnotation</span> {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">public</span> <span style="color:#8be9fd;font-style:italic">$description</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">public</span> <span style="color:#8be9fd;font-style:italic">$type</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Repare que, a classe em questão possui uma <strong>annotation</strong>, sendo ela <strong>@Annotation</strong>, isto é necessário para informar ao <strong>Doctrine Annotation Reader</strong> que a classe em questão, realmente é uma <strong>annotation</strong>.</p>
<h2 id="consumindo-a-annotation">Consumindo a annotation</h2>
<p>Nesta etapa, iremos consumir a annotation <strong>PeopleAnnotation</strong> e informaremos os valores que a annotation disponibiliza.</p>
<p>Salientando, é possível consumir a annotation em:</p>
<ul>
<li>classes;</li>
<li>atributos;</li>
<li>métodos.</li>
</ul>
<p>No exemplo abaixo, temos a classe <strong>People</strong>, que comsumirá a <strong>PeopleAnnotation</strong>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-php" data-lang="php"><span style="display:flex;"><span><span style="color:#ff79c6">&lt;?</span>php
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f1fa8c">/**
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">* @PeopleAnnotation(description=&#34;Get all information about a people&#34;, type=&#34;class&#34;)
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">*/</span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">class</span> <span style="color:#50fa7b">People</span> {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#f1fa8c">/**
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">    * @PeopleAnnotation(description=&#34;Use to people name&#34;, type=&#34;attribute&#34;)
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">    */</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">private</span> <span style="color:#8be9fd;font-style:italic">$name</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#f1fa8c">/**
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">    * @PeopleAnnotation(description=&#34;Use to people birth date&#34;, type=&#34;attribute&#34;)
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">    */</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">private</span> <span style="color:#8be9fd;font-style:italic">$birthDate</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#f1fa8c">/**
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">    * @PeopleAnnotation(description=&#34;Get people name&#34;, type=&#34;method&#34;)
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">    */</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">public</span> <span style="color:#ff79c6">function</span> <span style="color:#50fa7b">getName</span>() {
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">return</span> <span style="color:#8be9fd;font-style:italic">$this</span><span style="color:#ff79c6">-&gt;</span><span style="color:#50fa7b">name</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#f1fa8c">/**
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">    * @PeopleAnnotation(description=&#34;Get people birth date&#34;, type=&#34;method&#34;)
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">    */</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">public</span> <span style="color:#ff79c6">function</span> <span style="color:#50fa7b">getBirthDate</span>() {
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">return</span> <span style="color:#8be9fd;font-style:italic">$this</span><span style="color:#ff79c6">-&gt;</span><span style="color:#50fa7b">birthDate</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Repare que, a classe <strong>People</strong> está consumindo a <strong>annotation</strong> tanto na respectiva classe, quanto nos atributos e métodos.</p>
<h2 id="vamos-verificar-apeople-finalizando-a-sopa-de-letrinhas">Vamos verificar a <strong>People</strong>. Finalizando a sopa de letrinhas</h2>
<p>Nesta etapa final, iremos instanciar a classe <strong>AnnotationReader</strong> para lermos as <strong>annotations</strong> extraídas da classe <strong>People</strong> (que está consumindo a <strong>PeopleAnnotation</strong>).</p>
<p>Classes nativas utilizadas no exemplo:</p>
<ul>
<li><strong><a href="http://php.net/manual/pt_BR/class.reflectionclass.php">ReflectionClass()</a></strong>;</li>
<li><strong><a href="http://php.net/manual/pt_BR/class.reflectionobject.php">ReflectionObject()</a></strong>;</li>
<li><strong><a href="http://php.net/manual/pt_BR/class.reflectionproperty.php">ReflectionProperty()</a></strong>;</li>
<li><strong><a href="http://php.net/manual/pt_BR/class.reflectionmethod.php">ReflectionMethod()</a></strong>.</li>
</ul>
<p>Abaixo o exemplo:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-php" data-lang="php"><span style="display:flex;"><span><span style="color:#ff79c6">&lt;?</span>php
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">require_once</span> <span style="color:#f1fa8c">&#39;vendor/autoload.php&#39;</span>;
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">require_once</span> <span style="color:#f1fa8c">&#39;PeopleAnnotation.php&#39;</span>;
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">require_once</span> <span style="color:#f1fa8c">&#39;People.php&#39;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">use</span> Doctrine\Common\Annotations\AnnotationReader;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">$annotationReader</span> <span style="color:#ff79c6">=</span> <span style="color:#ff79c6">new</span> AnnotationReader();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">$reflectionClass</span> <span style="color:#ff79c6">=</span> <span style="color:#ff79c6">new</span> ReflectionClass(<span style="color:#f1fa8c">&#39;People&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">$classAnnotations</span> <span style="color:#ff79c6">=</span> <span style="color:#8be9fd;font-style:italic">$annotationReader</span><span style="color:#ff79c6">-&gt;</span><span style="color:#50fa7b">getClassAnnotations</span>(<span style="color:#8be9fd;font-style:italic">$reflectionClass</span>);
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">echo</span> <span style="color:#f1fa8c">&#34;CLASS ANNOTATIONS:&#34;</span>;
</span></span><span style="display:flex;"><span>var_dump(<span style="color:#8be9fd;font-style:italic">$classAnnotations</span>);
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">echo</span> <span style="color:#f1fa8c">&#39;&lt;hr/&gt;&#39;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">$people</span> <span style="color:#ff79c6">=</span> <span style="color:#ff79c6">new</span> People();
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">$reflectionObject</span> <span style="color:#ff79c6">=</span> <span style="color:#ff79c6">new</span> ReflectionObject(<span style="color:#8be9fd;font-style:italic">$people</span>);
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">$objectAnnotations</span> <span style="color:#ff79c6">=</span> <span style="color:#8be9fd;font-style:italic">$annotationReader</span><span style="color:#ff79c6">-&gt;</span><span style="color:#50fa7b">getClassAnnotations</span>(<span style="color:#8be9fd;font-style:italic">$reflectionObject</span>);
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">echo</span> <span style="color:#f1fa8c">&#34;OBJECT ANNOTATIONS:&#34;</span>;
</span></span><span style="display:flex;"><span>var_dump(<span style="color:#8be9fd;font-style:italic">$objectAnnotations</span>);
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">echo</span> <span style="color:#f1fa8c">&#39;&lt;hr/&gt;&#39;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">$reflectionProperty</span> <span style="color:#ff79c6">=</span> <span style="color:#ff79c6">new</span> ReflectionProperty(<span style="color:#f1fa8c">&#39;People&#39;</span>, <span style="color:#f1fa8c">&#39;name&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">$propertyAnnotations</span> <span style="color:#ff79c6">=</span> <span style="color:#8be9fd;font-style:italic">$annotationReader</span><span style="color:#ff79c6">-&gt;</span><span style="color:#50fa7b">getPropertyAnnotations</span>(<span style="color:#8be9fd;font-style:italic">$reflectionProperty</span>);
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">echo</span> <span style="color:#f1fa8c">&#34;PROPERTY ANNOTATION NAME:&#34;</span>;
</span></span><span style="display:flex;"><span>var_dump(<span style="color:#8be9fd;font-style:italic">$propertyAnnotations</span>);
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">echo</span> <span style="color:#f1fa8c">&#39;&lt;hr/&gt;&#39;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">$reflectionProperty</span> <span style="color:#ff79c6">=</span> <span style="color:#ff79c6">new</span> ReflectionProperty(<span style="color:#f1fa8c">&#39;People&#39;</span>, <span style="color:#f1fa8c">&#39;birthDate&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">$propertyAnnotations</span> <span style="color:#ff79c6">=</span> <span style="color:#8be9fd;font-style:italic">$annotationReader</span><span style="color:#ff79c6">-&gt;</span><span style="color:#50fa7b">getPropertyAnnotations</span>(<span style="color:#8be9fd;font-style:italic">$reflectionProperty</span>);
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">echo</span> <span style="color:#f1fa8c">&#34;PROPERTY ANNOTATION BIRTH DATE&#34;</span>;
</span></span><span style="display:flex;"><span>var_dump(<span style="color:#8be9fd;font-style:italic">$propertyAnnotations</span>);
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">echo</span> <span style="color:#f1fa8c">&#39;&lt;hr/&gt;&#39;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">$reflectionMethod</span> <span style="color:#ff79c6">=</span> <span style="color:#ff79c6">new</span> ReflectionMethod(<span style="color:#f1fa8c">&#39;People&#39;</span>, <span style="color:#f1fa8c">&#39;getName&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">$methodAnnotations</span> <span style="color:#ff79c6">=</span> <span style="color:#8be9fd;font-style:italic">$annotationReader</span><span style="color:#ff79c6">-&gt;</span><span style="color:#50fa7b">getMethodAnnotations</span>(<span style="color:#8be9fd;font-style:italic">$reflectionMethod</span>);
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">echo</span> <span style="color:#f1fa8c">&#34;Method ANNOTATIONS getName:&#34;</span>;
</span></span><span style="display:flex;"><span>var_dump(<span style="color:#8be9fd;font-style:italic">$propertyAnnotations</span>);
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">echo</span> <span style="color:#f1fa8c">&#39;&lt;hr/&gt;&#39;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">$reflectionMethod</span> <span style="color:#ff79c6">=</span> <span style="color:#ff79c6">new</span> ReflectionMethod(<span style="color:#f1fa8c">&#39;People&#39;</span>, <span style="color:#f1fa8c">&#39;getBirthDate&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">$methodAnnotations</span> <span style="color:#ff79c6">=</span> <span style="color:#8be9fd;font-style:italic">$annotationReader</span><span style="color:#ff79c6">-&gt;</span><span style="color:#50fa7b">getMethodAnnotations</span>(<span style="color:#8be9fd;font-style:italic">$reflectionMethod</span>);
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">echo</span> <span style="color:#f1fa8c">&#34;Method ANNOTATIONS getBirthDate: &#34;</span>;
</span></span><span style="display:flex;"><span>var_dump(<span style="color:#8be9fd;font-style:italic">$propertyAnnotations</span>);
</span></span></code></pre></div><h2 id="resultados">Resultados</h2>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"><code class="language-php" data-lang="php"><span style="display:flex;"><span><span style="color:#ff79c6">CLASS</span> <span style="color:#50fa7b">ANNOTATIONS</span><span style="color:#ff79c6">:</span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">array</span> (size<span style="color:#ff79c6">=</span><span style="color:#bd93f9">1</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#bd93f9">0</span> <span style="color:#ff79c6">=&gt;</span>
</span></span><span style="display:flex;"><span>    object(PeopleAnnotation)[<span style="color:#bd93f9">11</span>]
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">public</span> <span style="color:#f1fa8c">&#39;description&#39;</span> <span style="color:#ff79c6">=&gt;</span> string <span style="color:#f1fa8c">&#39;Get all information about a people&#39;</span> (length<span style="color:#ff79c6">=</span><span style="color:#bd93f9">34</span>)
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">public</span> <span style="color:#f1fa8c">&#39;type&#39;</span> <span style="color:#ff79c6">=&gt;</span> string <span style="color:#f1fa8c">&#39;class&#39;</span> (length<span style="color:#ff79c6">=</span><span style="color:#bd93f9">5</span>)
</span></span><span style="display:flex;"><span>OBJECT ANNOTATIONS<span style="color:#ff79c6">:</span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">array</span> (size<span style="color:#ff79c6">=</span><span style="color:#bd93f9">1</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#bd93f9">0</span> <span style="color:#ff79c6">=&gt;</span>
</span></span><span style="display:flex;"><span>    object(PeopleAnnotation)[<span style="color:#bd93f9">15</span>]
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">public</span> <span style="color:#f1fa8c">&#39;description&#39;</span> <span style="color:#ff79c6">=&gt;</span> string <span style="color:#f1fa8c">&#39;Get all information about a people&#39;</span> (length<span style="color:#ff79c6">=</span><span style="color:#bd93f9">34</span>)
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">public</span> <span style="color:#f1fa8c">&#39;type&#39;</span> <span style="color:#ff79c6">=&gt;</span> string <span style="color:#f1fa8c">&#39;class&#39;</span> (length<span style="color:#ff79c6">=</span><span style="color:#bd93f9">5</span>)
</span></span><span style="display:flex;"><span>PROPERTY ANNOTATION NAME<span style="color:#ff79c6">:</span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">array</span> (size<span style="color:#ff79c6">=</span><span style="color:#bd93f9">1</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#bd93f9">0</span> <span style="color:#ff79c6">=&gt;</span>
</span></span><span style="display:flex;"><span>    object(PeopleAnnotation)[<span style="color:#bd93f9">18</span>]
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">public</span> <span style="color:#f1fa8c">&#39;description&#39;</span> <span style="color:#ff79c6">=&gt;</span> string <span style="color:#f1fa8c">&#39;Use to people name&#39;</span> (length<span style="color:#ff79c6">=</span><span style="color:#bd93f9">18</span>)
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">public</span> <span style="color:#f1fa8c">&#39;type&#39;</span> <span style="color:#ff79c6">=&gt;</span> string <span style="color:#f1fa8c">&#39;attribute&#39;</span> (length<span style="color:#ff79c6">=</span><span style="color:#bd93f9">9</span>)
</span></span><span style="display:flex;"><span>PROPERTY ANNOTATION BIRTH DATE
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">array</span> (size<span style="color:#ff79c6">=</span><span style="color:#bd93f9">1</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#bd93f9">0</span> <span style="color:#ff79c6">=&gt;</span>
</span></span><span style="display:flex;"><span>    object(PeopleAnnotation)[<span style="color:#bd93f9">19</span>]
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">public</span> <span style="color:#f1fa8c">&#39;description&#39;</span> <span style="color:#ff79c6">=&gt;</span> string <span style="color:#f1fa8c">&#39;Use to people birth date&#39;</span> (length<span style="color:#ff79c6">=</span><span style="color:#bd93f9">24</span>)
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">public</span> <span style="color:#f1fa8c">&#39;type&#39;</span> <span style="color:#ff79c6">=&gt;</span> string <span style="color:#f1fa8c">&#39;attribute&#39;</span> (length<span style="color:#ff79c6">=</span><span style="color:#bd93f9">9</span>)
</span></span><span style="display:flex;"><span>Method ANNOTATIONS getName<span style="color:#ff79c6">:</span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">array</span> (size<span style="color:#ff79c6">=</span><span style="color:#bd93f9">1</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#bd93f9">0</span> <span style="color:#ff79c6">=&gt;</span>
</span></span><span style="display:flex;"><span>    object(PeopleAnnotation)[<span style="color:#bd93f9">19</span>]
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">public</span> <span style="color:#f1fa8c">&#39;description&#39;</span> <span style="color:#ff79c6">=&gt;</span> string <span style="color:#f1fa8c">&#39;Use to people birth date&#39;</span> (length<span style="color:#ff79c6">=</span><span style="color:#bd93f9">24</span>)
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">public</span> <span style="color:#f1fa8c">&#39;type&#39;</span> <span style="color:#ff79c6">=&gt;</span> string <span style="color:#f1fa8c">&#39;attribute&#39;</span> (length<span style="color:#ff79c6">=</span><span style="color:#bd93f9">9</span>)
</span></span><span style="display:flex;"><span>Method ANNOTATIONS getBirthDate<span style="color:#ff79c6">:</span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">array</span> (size<span style="color:#ff79c6">=</span><span style="color:#bd93f9">1</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#bd93f9">0</span> <span style="color:#ff79c6">=&gt;</span>
</span></span><span style="display:flex;"><span>    object(PeopleAnnotation)[<span style="color:#bd93f9">19</span>]
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">public</span> <span style="color:#f1fa8c">&#39;description&#39;</span> <span style="color:#ff79c6">=&gt;</span> string <span style="color:#f1fa8c">&#39;Use to people birth date&#39;</span> (length<span style="color:#ff79c6">=</span><span style="color:#bd93f9">24</span>)
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">public</span> <span style="color:#f1fa8c">&#39;type&#39;</span> <span style="color:#ff79c6">=&gt;</span> string <span style="color:#f1fa8c">&#39;attribute&#39;</span> (length<span style="color:#ff79c6">=</span><span style="color:#bd93f9">9</span>)
</span></span></code></pre></div><h2 id="referências">Referências</h2>
<ol>
<li><a href="http://masnun.com/2012/08/12/using-annotations-in-php-with-doctrine-annotation-reader.html">Artigo sobre o assunto em inglês</a></li>
<li><a href="http://doctrine-common.readthedocs.org/en/latest/reference/annotations.html">Doctrine - Documentação oficial</a></li>
<li><a href="http://php.net/">Documentação oficial PHP.net</a></li>
</ol>
<h2 id="conclusão">Conclusão</h2>
<p>A utilização de <strong>annotation</strong> pode facilitar diversas condições, salientando que, a necessidade de implementar <strong>custom annotation</strong> varia de situação. Use o bom senso de programador.</p>
<p>Quaisquer feedbacks serão bem-vindos, fique à vontade para comentar e/ou implementar alguma informação.</p>
<p>Até breve méros mortais e eternos aprendizes (todos somos).</p>
]]></content:encoded><category>PHP</category></item><item><title>Como replicar banco de dados MySQL</title><link>https://rifeli.dev/blog/2015-11-11-como-replicar-banco-de-dados-mysql/</link><pubDate>Wed, 11 Nov 2015 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/2015-11-11-como-replicar-banco-de-dados-mysql/</guid><description>Saudações, atuo como desenvolvedor Full-Stack em uma empresa de Araraquara (SP), a wab.com.br. Não sou um expert em servidores e aplicações atuando como tal. Porém gosto de estudar, então pratiquei sobre o assunto a qual os escrevo.</description><content:encoded><![CDATA[<p>Saudações, atuo como desenvolvedor Full-Stack em uma empresa de Araraquara (SP), a wab.com.br. Não sou um expert em servidores e aplicações atuando como tal. Porém gosto de estudar, então pratiquei sobre o assunto a qual os escrevo.</p>
<p>Iremos abordar os seguintes tópicos:</p>
<ol>
<li>Introdução</li>
<li>Funcionamento da replicação (teoria)</li>
<li>Como iremos replicar (esquema)</li>
<li>Replicando um banco de dados MySQL (prática)</li>
</ol>
<h2 id="introdução">Introdução</h2>
<p>A replicação de bancos de dados tem como principal objetivo a <strong>redundância</strong>, onde torna-se uma aplicação mais segura contra falhas e <strong>indisponibilidades de outras aplicações (como o respectivo banco de dados)</strong> e por sua vez um backup online dos dados (em tempo real). Nos tópicos abaixo estaremos abordando todos os processos de replicação.</p>
<h2 id="como-funciona-a-replicação">Como funciona a replicação?</h2>
<p>O <strong>MySQL</strong> possui um recurso de comunicação (onde ocorrerá a replicação) modo <strong>Master-Slave</strong>. Sendo assim, um servidor poderá possuir um banco de dados MySQL rodando como <strong>Master</strong> e <strong>N</strong> bancos de dados atuando como <strong>Slave</strong> (em diferentes servidores).</p>
<p>O servidor atuando em modo <strong>Master</strong> irá gravar todas as alterações efetuadas no banco de dados, em um arquivo de <strong>log binário</strong>. Onde o servidor atuando em modo <strong>Slave</strong> irá requisitar o log binário do <strong>Master</strong> e alterará o próprio arquivo de log binário deixando-o idêntico, onde o MySQL atual aplicando as alterações em si.</p>
<p>Segue abaixo um esquema de replicação <strong>Master-Slave</strong>:</p>
<p><img src="https://rifeli.me/img/posts/2015-11-01-mysql-replication.jpg" alt="https://rifeli.me/img/posts/2015-11-01-mysql-replication.jpg"></p>
<p><strong>Figura 1.1 - Fonte: Google Images</strong></p>
<h2 id="como-iremos-replicar-esquema">Como iremos replicar (esquema)</h2>
<p>Para a replicação do <strong>MySQL</strong>, será utilizado três instâncias <strong>t2.micro</strong> no <strong>Amazon AWS</strong>, conforme abaixo:</p>
<p><img src="https://rifeli.me/img/posts/2015-11-01-amazon-aws.png" alt="https://rifeli.me/img/posts/2015-11-01-amazon-aws.png"></p>
<p><strong>Instâncias - Figura 1.2</strong></p>
<p><strong>Observação:</strong> Pode-se analisar na <strong>Figura 1.2</strong> (acima) que as três instâncias encontram-se no mesmo datacenter, porém a replicação também funciona em datacenters diferentes.</p>
<p>Onde os respectivos atuarão em modo:</p>
<ol>
<li><strong>leonardorifeli-001:</strong> Master</li>
<li><strong>leonardorifeli-002:</strong> Slave</li>
<li><strong>leonardorifeli-003:</strong> Slave</li>
</ol>
<p>Conforme esquema abaixo:</p>
<p><img src="https://rifeli.me/img/posts/2015-11-01-server-aws-mysql-replication.png" alt="https://rifeli.me/img/posts/2015-11-01-server-aws-mysql-replication.png"></p>
<p><strong>Figura 1.3</strong></p>
<p><strong>PS</strong>: Não entrarei em detalhes sobre o Amazon AWS.</p>
<h2 id="replicando-um-banco-de-dados-mysql">Replicando um banco de dados MySQL</h2>
<p>Vamos ao tão esperado tópico.</p>
<p>As instâncias especificadas na <strong>Figura 1.2</strong> estão rodando com o sistema operacional <strong>Ubuntu Server 14.04 LTS (HVM), SSD Volume Type</strong>, disponibilizado pelo Amazon AWS.</p>
<p>Para a replicação do banco de dados <strong>MySQL</strong> será necessário a instalação da respectiva aplicação (óbvio). Neste post foi utilizado o <strong>mysql-server-5.6</strong>. <a href="http://sharadchhetri.com/2014/05/07/install-mysql-server-5-6-ubuntu-14-04-lts-trusty-tahr/">Tutorial de instalação</a></p>
<p>Iremos aplicar os seguintes passos:</p>
<ol>
<li><strong>Servidor Master:</strong> Configurar o servidor Master (arquivo my.cnf)</li>
<li><strong>Servidor Master:</strong> Criar o usuário de replicação e conceder as devidas permissões</li>
<li><strong>Servidores Slaves:</strong> Configurar o servidor (arquivo my.cnf)</li>
<li><strong>Servidores Slaves:</strong> Informar qual será o servidor <strong>Master</strong></li>
</ol>
<h4 id="1-configuração-do-servidor-master">1. Configuração do servidor Master</h4>
<p>Após a instalação com sucesso do <strong>MySQL Server 5.6</strong> nas instâncias especificadas na <strong>Figura 1.2</strong>, vamos as respectivas configurações na instância <strong>leonardorifeli-001</strong> que atuará em modo <strong>Master</strong>.</p>
<p>Configurando:</p>
<p><code>$ cd /etc/mysql $ nano my.cnf</code></p>
<p>Será necessário editar o arquivo <strong>my.cnf</strong>, ficando da seguinte maneira:</p>
<p><code>[mysqld] server-id = 1 log-bin = mysql-bin bind-address = 0.0.0.0</code></p>
<p>Indicamos que o respectivo servidor será o <strong>Master</strong> (pelo server-id). Após editar, executamos o comando abaixo (para reiniciar o servidor MySQL):</p>
<p><code>$ service mysql restart</code></p>
<h4 id="2-criar-usuário-de-replicação-e-conceder-as-permissões">2. Criar usuário de replicação e conceder as permissões</h4>
<p>Agora, iremos criar o usuário para utilizarmos na replicação e conceder para tal as devidas permissões.</p>
<p>Acessando o <strong>MySQL</strong> com usuário root e informando a senha (informado na instalação).</p>
<p><code>$ mysql -u root -p</code></p>
<p><code>CREATE USER slave IDENTIFIED BY &quot;user-rifeli&quot;; GRANT replication SLAVE, replication CLIENT ON *.* TO slave@'IP-LEONARDORIFELI-002' IDENTIFIED BY 'user-rifeli'; GRANT replication SLAVE, replication CLIENT ON *.* TO slave@'IP-LEONARDORIFELI-003' IDENTIFIED BY 'user-rifeli'; FLUSH PRIVILEGES;</code></p>
]]></content:encoded><category>MySQL</category></item><item><title>Como ir bem em cálculo 1</title><link>https://rifeli.dev/blog/2015-07-12-como-ir-bem-em-calculo-um/</link><pubDate>Sun, 12 Jul 2015 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/2015-07-12-como-ir-bem-em-calculo-um/</guid><description>Sim, é possível, não desista sem ao menos ter sentado a ‘bunda’ na cadeira e estudado.</description><content:encoded><![CDATA[<p>Sim, é possível, não desista sem ao menos ter sentado a ‘bunda’ na cadeira e estudado.</p>
<h2 id="introdução">Introdução</h2>
<p>Deixando claro que descreverei minhas experiências cursando bacharelado em Engenharia de Computação.</p>
<p>Em meu primeiro semestre dentro de uma instituição acadêmica deparei-me com a disciplina que mais amedontra alguns alunos de <strong>exatas</strong>, sim, o <strong>cálculo</strong>. Porém, ao decorrer do primeiro semestre, ainda estudando cálculo 1, percebi que não é tão difícil como todos dizem (não estou me gabando), então, decidi escrever como consegui dar-me bem (ao menos passei) em tal disciplina.</p>
<h2 id="um-pouco-sobre-mim">Um pouco sobre mim</h2>
<p>Eu estudei todo o ensino inicial (fundamental e médio) em <strong>escola pública</strong> (E.E Ergilia Micelli), e de certa forma não foi um ensino de qualidade para preparar-me para o ensino superior, mas sempre pensei: é o aluno que define a instituição, então eu sempre me empenhei na medida do possível.</p>
<p>Tive uma preparação excelente no curso pré-vestibular <a href="http://www.iq.unesp.br/#!/cuca/">CUCA</a> (Curso Unificado do Câmpus de Araraquara) durante um ano (2014), onde tive contato também com outras áreas do conhecimento <strong>(posso descrever detalhes em outro post)</strong>.</p>
<p>Ao deparar-me com o ensino superior, foi uma surpresa e também uma grande experiência.</p>
<h2 id="informações-gerais">Informações gerais</h2>
<p>Se você se diz não gostar de cálculo, revise seus conceitos sobre exatas. Sempre é dito nas primeiras aulas na faculdade: - <strong>“Será como na escola, porém, acelerado, bem acelerado”</strong>. Isso eu vivênciei e consegui superar meus limites, principalmente na disciplina de cálculo, a cada aula foi algo novo para mim, a princípio poderá ser para você.</p>
<p>Tenha em mente que você terá que empenhar-se para seus objetivos serem realizados, você terá que suar muito, ler muito, e principalmente,fazer muitos exercícios.</p>
<p>Salientando se você chegou na aula de cálculo e disse não gostar da matéria, por gentileza, revise seus conceitos (é isso mesmo que você deseja?), pois isso poderá ser prejudicial ao seu futuro.</p>
<p>Agora, se você gosta, mas possui dificuldades, continue empenhando-se que conseguirá.</p>
<h2 id="mas-como">Mas como?</h2>
<p>Vá para a aula, evite faltar e caso isso aconteça, pegue a matéria urgentemente e esclareça suas dúvidas com alguém ou até mesmo com o Professor.</p>
<p>Participe das aulas, tire suas dúvidas quanto a teoria dos assuntos abordados, pegue um livro de cálculo e prátique, faça exercícios. Na área de sugestões segue alguns livros que utilizei.</p>
<p>Como meu sábio Professor <strong><a href="http://www.escavador.com/pessoas/5614163">Marcelo Mana</a></strong> disse: - “Sente e faça cinquenta, cem, duzentos exercícios e quero ver falar que não aprendeu”.</p>
<h2 id="algumas-dicas">Algumas dicas</h2>
<ol>
<li><strong>Tenha foco:</strong> Sua mão escreve sozinha, você não precisa ficar olhando para ela. É possível prestar atenção e escrever ao mesmo tempo. Você não anda conversando? Então.</li>
<li><strong>Anotações:</strong> Tente escrever palavras chaves durante a aula para não se perder quando for estudar.</li>
<li><strong>Evite faltas:</strong> Sim, elas prejudicam. Caso aconteça pegue a matéria que perdeu com alguém e esclareça suas dúvidas.</li>
<li><strong>Presença constante na biblioteca:</strong> Sabe aqueles minutos de intervalo, ou quando a aula finaliza mais cedo? Então, use-os a seu favor, vá para biblioteca e coloque às dúvidas em dia.</li>
<li><strong>Faça exercícios:</strong> Aproveitando a dica anterior, pegue livros respectivos de cálculo para aplicar e exercitar. Leia as sugestões de livros logo abaixo.</li>
<li><strong>Persista:</strong> Por mais complicado que seja a leitura dos livros de exatas, persista, leia e releia (no Youtube é possível encontrar videos que facilitam). Não desista. Se o cara teve capacidade intelectual de escrever, você terá de ler e entender.</li>
</ol>
<h2 id="sugestões">Sugestões</h2>
]]></content:encoded><category>Matemática</category></item><item><title>Por que utilizar o Jekyll?</title><link>https://rifeli.dev/blog/2015-05-05-porque-utilizar-o-jekyll/</link><pubDate>Tue, 05 May 2015 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/2015-05-05-porque-utilizar-o-jekyll/</guid><description>Neste post irei descrever por que optei por utilizar o Jekyll. Mostrarei como ele é e como funciona.</description><content:encoded><![CDATA[<p>Neste post irei descrever por que optei por utilizar o Jekyll. Mostrarei como ele é e como funciona.</p>
<h2 id="introdução">Introdução</h2>
<p>Primeiramente irei explicar o que é o Jekyll.</p>
<p><strong>Jekyll</strong> é um gerenciador de códigos estáticos. Isso mesmo, ele não faz uso de banco de dados e não requesita um servidor robusto para funcionar, podendo utilizar o <strong>Github Pages</strong> para hospedar o site. Você pode desenvolver páginas e até mesmo um blog de forma estática, apenas utilizando <strong>HTML</strong> que você provavelmente já conhece. Ele é baseado em vários formatos como <strong>Markdown</strong> para formatação de textos e posts e um padrão de template chamado <strong>Liquid</strong> com um pouco de <strong>YAML</strong> para exibir e guardar os dados das variáveis.</p>
<p>Mostrarei como o <strong>Jekyll</strong> funciona (o que eu aprendi até aqui) posteriormente como iniciar um projeto utilizando-o.</p>
<h2 id="estrutura-de-diretórios">Estrutura de diretórios</h2>
<p>Todo arquivo e/ou diretório que tiver <strong>underscore (exemplo: _includes)</strong> no começo, o <strong>Jekyll</strong> irá ignorar no pacote final, quando rodar o <strong><code>jekyll build</code></strong> para gerar os arquivos para o site (os arquivo do site ficarão dentro do diretório <strong>_site</strong>).</p>
<p>O diretório <strong>_includes</strong> guarda arquivos que serão reutilizados nas páginas do projeto, como, <strong>header</strong>, <strong>footer</strong>, <strong>sidebar</strong>, <strong>nav</strong> ou qualquer outra coisa de acordo com o layout.</p>
<p>No diretório <strong>_layouts</strong> você vai colocar os padrões de layout de páginas. Imagine que existam páginas com formatos de estruturas diferentes. Exemplo: <strong>default.html</strong>, <strong>article.html</strong>, <strong>post.html</strong>, e assim por diante.</p>
<p>O diretório <strong>_site</strong> é o <strong>build</strong> do seu projeto. É ali que o <strong>Jekyll</strong> coloca a versão final estática do site (que é gerado executando <strong><code>jekyll build</code></strong> no terminal), pronto para ser publicado.</p>
<p>Há pessoas que preferem deixar o diretório <strong>_site</strong> versionável no GIT, e há pessoas que o colocam no <strong>.gitignore</strong> e utilizam outro diretório (<strong>web</strong> por exemplo). Basta executar o <strong><code>jekyll build</code></strong> copiar os arquivos gerados no diretório <strong>_site</strong> para o diretório <strong>web</strong>.</p>
<p><code>|-- _config.yml |-- _includes/ |-- _layouts/ |-- _posts/ |-- _site/ |-- about.md # =&gt; será uma página chamada about |-- index.html # =&gt; http://projeto.com └── feed.xml # =&gt; http://projeto.com/feed.xml</code></p>
<h2 id="yaml">YAML</h2>
<p>O formato YAML foi desenvolvido para facilidar o entendimento e a escrita dentro dos arquivos no respectivo formato.</p>
<p>Qualquer arquivo no respectivo formato e que contenha um bloco em YAML será processado pelo jekyll como um arquivo especial, o pessoal do <strong>Jekyll</strong> o chama de <strong>front-matter</strong>. O front-matter precisa estar em um formato válido de YAML. Toda a página no Jekyl deverá ser iniciada da seguinte maneira:</p>
<p><code>--- layout: default title: Home ---</code></p>
<p>Restritamente deverá começar com os três traços e finalizar com os mesmos. Sem choro nem vela. O código YAML são as variáveis <strong><code>layout</code></strong> e <strong><code>title</code></strong></p>
<h2 id="entendendo-o-arquivo-_configyml">Entendendo o arquivo _config.yml</h2>
<p>Tal arquivo é responsável por armazenar as variável que serão utilizadas dentro do site. Exemplo: Copyright do footer, link das redes sociais, e-mail, mensagens, etc.</p>
<p>Exemplo:</p>
<p>`## SITE CONFIGURATION
baseurl: &quot;&quot;
url: &ldquo;<a href="https://leonardorifeli.com">https://leonardorifeli.com</a>&rdquo;</p>
<h3 id="theme-specific-configuration">THEME-SPECIFIC CONFIGURATION</h3>
<p>theme: title: Leonardo Rifeli email: <a href="mailto:leonardorifeli@gmail.com">leonardorifeli@gmail.com</a> empresa: Wab &lt;wab.com.br&gt; facebook: false twitter: true twitter_base: leonardorifeli`</p>
<p>Para utilizar as variáveis dentro do site, segue um exemplo implementado no arquivo <strong><code>_includes/head.html</code></strong>:</p>
<p><code>&lt;**title**&gt;{</code>{ site.theme.title }<code>}&lt;/**title**&gt;&lt;**link** rel=&quot;stylesheet&quot; href=&quot;{</code>{ /css/main.css&quot; | prepend: site.baseurl }<code>}&quot;&gt;</code></p>
<p><strong>Obs.:</strong> Removendo as aspas simples, ele irá utilizar o valor que foi armazenado em cada respectiva variável.</p>
<h2 id="iniciando">Iniciando</h2>
<p>Bom, após uma descrição de como o Jekyll funciona (ou melhor, o que aprendi até agora). Irei demonstrar como iniciar um novo projeto utilizando o <strong>Jekyll</strong>. Irei demonstrar utilizando um repositório no <strong>Github</strong> e hospedando no <strong>Github Pages</strong>.</p>
<p>Acesse sua conta no Github, crie um novo repositório com o nome da organização e utilizando o sufixo <strong>.github.io</strong>. Exemplo: <strong>leonardorifeli.github.io</strong>.</p>
<p>Em seguido acesse a página do repositório e vá em <strong>“Settings”</strong> e no box <strong>“GitHub Pages”</strong> clique em <strong>“Automatic page generator”</strong>, na etapa seguinte clique em <strong>“Continue to layouts”</strong>. O próximo pasos será selecionar um layout (não se preocupe muito quanto a isso), simplesmente clique em <strong>“Publish page”</strong>.</p>
<p>Após finalizar clone o repositório.</p>
<h2 id="dependências">Dependências</h2>
<p>Para iniciar um projeto com Jekyll será necessário instalar:</p>
<ol>
<li><a href="https://www.ruby-lang.org/en/downloads/">Ruby</a>;</li>
<li><a href="https://rubygems.org/pages/download">RubyGems</a>.</li>
<li>Linux, Unix, or Mac OS X;</li>
<li><a href="https://nodejs.org/">NodeJS</a>, or another JavaScript runtime.</li>
</ol>
<p>PS: Mais informações quanto a instalação das dependências, você encontra na <a href="http://jekyllrb.com/docs/installation/">documentação oficial do <strong>Jekyll</strong></a></p>
<h2 id="instalando">Instalando</h2>
<p>Após ter instalado as dependências citadas acima, instale o <strong>Jekyll</strong>:</p>
<p><code>gem install jekyll</code></p>
<p><strong>Vá para o diretório do repositório clonado</strong> remova todos os arquivos que vieram junto ao repositório, deixando o diretório vazio e inicie um projeto com o Jekyll, executando (dentro do diretório do repositório clonado):</p>
<p><code>jekyll new ./</code></p>
<p>Você pode executar <strong><code>jekyll server</code></strong>, automaticamente ele executará <strong><code>jekyll build</code></strong> para gerar o diretório <strong>_site</strong> com os arquivos estátivos. Você poderá verificar acessando <strong>localhost:4000</strong>.</p>
<h2 id="recomendação">Recomendação</h2>
<p>Você pode apenas comitar as alterações, ou adicionar o diretório <strong>_site</strong> no ignore do GIT, criar um diretório <strong>web</strong> por exemplo, copiar/colar os arquivos gerados no diretório <strong>_site</strong> para o diretório <strong>web</strong> e comitar.</p>
<p>A primeira opção é recomendada caso vá utilizar o Github Page e a segunda opção é recomendada caso vá utilizar um servidor mesmo assim.</p>
<h2 id="aparência">Aparência</h2>
<p>Quanto ao templete/tema você pode desenvolver um, apenas fique familiarizado com o Jekyll para entender toda a estrutura e desenvolver sem impecilios. Ou, na internet existe vários sites que disponibilizam e/ou vendem templates/temas para Jekyll.</p>
<p><strong>Recomendação:</strong> <a href="http://jekyllthemes.org/">jekyllthemes.org</a></p>
<h2 id="comandos">Comandos</h2>
<p><strong><code>jekyll build</code></strong>: Irá gerar as páginas estáticas de acordo com os arquivos de configuração e os demais.</p>
<p><strong><code>jekyll build --watch</code></strong>: Deixa o build verificando quando um arquivo é alterado gerando o build novamente. Recomendado utilizar quando estiver desenvolvendo e/ou implementando o template/tema.</p>
<p><strong><code>jekyll server</code></strong>: Inicia um servidor, como default em http://localhost:4000/ para acesso local. Só será utilizado para quando estiver esenvolvendo e/ou implementando o template/tema.</p>
<p><strong><code>jekyll -h</code></strong>: Todos os demais comandos disponíveis.</p>
<h2 id="não-recomendo">Não recomendo</h2>
<p>Nem pense em utiliza-lo com <strong>sites</strong> mais <strong>robustos</strong>, com <strong>internacionalização</strong>, <strong>controle</strong> disso, <strong>controle</strong> daquilo. O <strong>Jekyll</strong> é simples, objetivo e não é robusto nestes aspectos.</p>
<h2 id="referências">Referências</h2>
<ol>
<li><strong>Outro artigo sobre o Jekyll:</strong> <a href="http://tableless.com.br/jekyll-servindo-sites-estaticos/">tableless.com.br/jekyll-servindo-sites-estaticos</a>;</li>
<li><strong>Documentação oficial:</strong> <a href="http://jekyllrb.com/">jekyllrb.com</a></li>
<li><strong>Github pages:</strong> <a href="https://pages.github.com/">pages.github.com</a></li>
<li><strong>Repositório:</strong> <a href="https://github.com/leonardorifeli/leonardorifeli.github.io">utilizado no artigo</a></li>
</ol>
<h2 id="conclusão">Conclusão</h2>
<p>Optei pela utilização do <strong>Jekyll</strong> pelo fato de não necessitar de um servidor robusto para o site funcionar e pela facilidade de alteração, inclusão de novos artigos e/ou páginas. É somente criar um arquivo em <strong>YAML</strong> e digitar o escopo utilizando <strong>Markdown</strong>, executar o <strong><code>jekyll build</code></strong> e comitar, pronto, sem dificuldades, resolvido. O Jekyll abre um leque de opções de personalização, agilidade no desenvolvimento de sites pequenos (sim, eu disse pequenos!).</p>
<p>Futuramente irei postar um artigo com melhor aprofundamento no <strong>Github Pages</strong>, descrevendo as funcionalidades, customizar domínio, e por aí vai.</p>
<p><strong>PS:</strong> Qualquer erro ortográfico e/ou digitação, reporte-os, auxilia-nos a evolução!</p>
]]></content:encoded><category>Blog</category></item><item><title>Instalando certificado de segurança SSL</title><link>https://rifeli.dev/blog/2015-04-01-instalando-certificado-de-seguranca-ssl/</link><pubDate>Wed, 01 Apr 2015 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/2015-04-01-instalando-certificado-de-seguranca-ssl/</guid><description>O SSL (Secure Socket Layer) é um protocolo desenvolvido para elevar a segurança dos dados transmitidos pela internet. O SSL pode ser usado em vários serviços, sendo que o mais comum é o acesso à páginas web.</description><content:encoded><![CDATA[<p>O SSL <strong>(Secure Socket Layer)</strong> é um protocolo desenvolvido para elevar a segurança dos dados transmitidos pela internet. O SSL pode ser usado em vários serviços, sendo que o mais comum é o acesso à páginas web.</p>
<h2 id="introdução">Introdução</h2>
<h4 id="antes-de-tudo-o-que-é-ssl">Antes de tudo. O que é SSL?</h4>
<p>O <strong>SSL (Secure Socket Layer)</strong> é um protocolo desenvolvido para elevar a segurança dos dados transmitidos pela internet. O SSL pode ser usado em vários serviços, sendo que o mais comum é o acesso à páginas web. Neste caso o endereço dos recursos acessados passa a ser feito no formato: <code>https://website</code>.</p>
<p>As conexões via SSL são particularmente recomendadas para envio de informações como números de cartão de crédito, senhas e qualquer outra informação sigilosa via internet.</p>
<p>O SSL faz uso de <strong>criptografia</strong> para garantir o sigilo das informações transferidas entre o navegador do usuário e o servidor web. Como consequência, mesmo que as informações sejam interceptadas elas não podem ser lidas sem que sejam <strong>descriptografadas</strong>.</p>
<h4 id="o-que-é-um-certificado-ssl">O que é um certificado SSL?</h4>
<p>O certificado SSL tem a função de certificar que o site que você está acessando é realmente ele mesmo. Este processo é realizado por empresas que emitem certificados SSL. Elas fazem a validação do domínio e, dependendo do tipo de certificado, também da entidade detentora do domínio.</p>
<p>Sempre que você acessa uma página segura, isto é, protegida por um certificado SSL, é apresentada uma chave ou um cadeado na barra de status para indicar a comunicação segura. Os certificados tradicionais exigem que você clique na chave ou cadeado para ter acesso às informações do detentor do certificado SSL.</p>
<p>Texto retirado do site: centralserver.com.br clique aqui para visualizar o texto completo.</p>
<p>O domínio leonardorifeli.com possuí o certificado SSL COMODO 256bits. Efetuei a compra do certificado por fins didáticos (estudos para configuração). Estarei demonstrando como efetuei a instalação do certificado no servidor.</p>
<p><strong>Obs.:</strong> Salientando que não demonstrarei como efetuar a compra/solicitação do certificado.</p>
<h2 id="informações">Informações</h2>
<p>Sistema Operacional (servidor): Ubuntu Server 14.04; Dependências: apache2, openssl e ssl-cert;</p>
<h4 id="dependências">Dependências</h4>
<p>As depências são os recursos/bibliotecas utilizadas para a gerar e configurar o certificado. Faça acesso SSH com o servidor, utilizando privilégios de root (oh my god! Yes!).</p>
<p>Execute os comandos abaixo para atualização de dependências já instaladas. Posteriormente, certifique-se que: apache2, openssl e ssl-cert encontram-se instalados no ambiente.</p>
<p><code>apt-get update apt-get upgrade apt-get install apache2 openssl ssl-cert</code></p>
<h2 id="gerando-as-chaves">Gerando as chaves</h2>
<p>Criando o diretório onde os certificados serão armazenados.</p>
<p><code>mkdir /etc/apache2/ssl/</code></p>
<p>Acessando o diretório dos certificados.</p>
<p><code>cd /etc/apache2/ssl/</code></p>
<p>Gerando as chaves. Atente-se leia os descritivos abaixo antes de acionar enter.</p>
<p><code>openssl req -nodes -newkey rsa:2048 -keyout dominio.com.key -out dominio.com.csr</code></p>
<p>Primeiro entenderemos o que o comando acima executará.</p>
<p>O comando em questão irá gerar o arquivo <strong>dominio.com.key</strong>, o qual contém a chave privada e <strong>não deverá em hípotese alguma, ser fornecido há alguém</strong>.</p>
<p>De imediato, certifique-se de fazer o backup da chave privada <strong>(dominio.com.key)</strong>, pois não há meios de recuperá-la. A chave privada é utilizada como entrada no processo para gerar um <strong>“Pedido de Assinatura de Certificado (CSR)”.</strong> O CSR é um arquivo contendo as informações da solicitação do certificado (logo você preencherá algumas informações), incluindo sua chave pública.</p>
<p>Agora que foi informado a função do comando citado, pode acionar enter, logo você preencherá as informações para o certificado:</p>
<p>Veja como as perguntas serão feitas e como respondê-las:</p>
<p><code>Country Name (2 letter code) [AU]: BR State or Province Name (full name) [Some-State]: SP Locality Name (eg, city) []: Araraquara Organization Name (eg, company) [Internet Widgits Pty Ltd]: Nome da empresa (deixe em branco caso não possua) Organizational Unit Name (eg, section) []: Cargo na empresa (deixe em branco caso não possua) Common Name (eg, YOUR name) []: dominio.com Email Address []: contato@dominio.com Please enter the following ‘extra’ attributes to be sent with your certificate request A challenge password []: aperte enter (deixar em branco) An optional company name []: aperte enter (deixar em branco)</code></p>
<p>Após a finalização, o arquivo <strong>dominio.com.key</strong> será criado. Deixe-o com permissão de acesso 600.</p>
<p><code>chmod 600 dominio.com.key</code></p>
<p>Após finalizar, certifique-se que dois arquivos foram criados, <strong>dominio.com.key</strong> e <strong>dominio.com.csr</strong>. Lembre-se que o conteúdo do arquivo <strong>“dominio.com.csr”</strong> deve ser utilizado para finalizar a solicitação de registro do certificado (colar no campo “Enter CSR”). Efetuei a compra no site Comodo.</p>
<p>Pegando o conteúdo do arquivo <strong>dominio.com.csr</strong>.</p>
<p><code>cat dominio.com.csr</code></p>
<p>Após finalizar a compra, o provedor do certificado envia os seguintes arquivos para serem instalados no servidor:</p>
<ol>
<li><strong>Root CA Certificate:</strong> AddTrustExternalCARoot.crt</li>
<li><strong>Intermediate CA Certificate:</strong> AAddTrustCA.crt</li>
<li><strong>Intermediate CA Certificate:</strong> DomainValidationSecureServerCA.crt</li>
<li><strong>Your PositiveSSL Certificate:</strong> (dominio).crt</li>
</ol>
<p>PS: A empresa <strong>Comodo</strong> enviou os arquivos acima via e-mail. Lembre-se, guarde-os com segurança.</p>
<p>Faça upload dos arquivos no diretório, <code>/etc/apache2/ssl</code>.</p>
<p>Os três primeiros arquivos serão utilizados para gerar o arquivo que será utilizado pelo <code>Apache</code> <strong>(criar arquivo com extensão ”.ca-bundle”)</strong>. Mergeie (mesclar, juntar) os conteúdos em um único arquivo com nome “dominio.ca-bundle, utilizando o comando.</p>
<p><code>cat AAddTrustCA.crt DomainValidationSecureServerCA.crt AddTrustExternalCARoot.crt &gt; www.dominio.com.ca-bundle</code></p>
<h2 id="configuração-no-apache">Configuração no Apache</h2>
<p>Com o arquivo <code>.ca-bundle</code> criado, efetuaremos a configuração no arquivo <code>.conf</code> do respectivo domínio (no apache).</p>
<p><code>cd /etc/apache2/sites-available nano arquivo-configuracao-utilizado.conf</code></p>
<p>Segue abaixo um modelo em funcionamento (você poderá utilizá-lo como base).</p>
<h4 id="configuração-da-porta-80-para-redirecionar-os-acessos-http-para-https-porta-443">Configuração da porta 80 para redirecionar os acessos http para https, porta 443;</h4>
<p><code>&lt;VirtualHost *:80&gt; ServerAdmin your@dominio.com ServerName dominio.com ServerAlias www.dominio.com RewriteEngine On RewriteCond %{HTTPS} off RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} &lt;/VirtualHost&gt;</code></p>
<h4 id="configuração-da-porta-443-onde-o-site-funcionará-acesso-https">Configuração da porta 443, onde o site funcionará (acesso https);</h4>
<p><code>&lt;VirtualHost *:443&gt; ServerName www.dominio.com:443 ServerAdmin your@dominio.com ServerName dominio.com ServerAlias www.dominio.com DocumentRoot /var/www/website DirectoryIndex index.php index.htm index.html TransferLog /var/log/apache2/website.log SSLEngine on SSLCertificateKeyFile /etc/apache2/ssl/www.dominio.com.key SSLCertificateFile /etc/apache2/ssl/www.dominio.com.crt SSLCertificateChainFile /etc/apache2/ssl/www.dominio.com.ca-bundle ServerSignature off &lt;Directory &quot;/var/www/website&quot;&gt; AllowOverride All &lt;/Directory&gt; ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined &lt;/VirtualHost&gt;</code></p>
<p>Após finalizar a configuração.</p>
<p><code>service apache2 restart</code></p>
<p>Caso dê algum erro, verifique os logs (pois é para isso que eles existem). Brincadeiras, entre em contato, ficarei feliz em dar auxilio na medida do possível.</p>
<h2 id="conclusão">Conclusão</h2>
<p>A utilização de certificado de segurança em sites/sistemas que possuem tráfego de informações privilegiadas é imprescindível. Portanto, certifique-se que as informações contídas nas áreas restritas, encontram-se seguras.</p>
<p>Espero ter-lhe auxiliado em algum aspecto. Até o próximo post.</p>
]]></content:encoded><category>Linux</category></item><item><title>PHP Expirience 2015</title><link>https://rifeli.dev/blog/2015-03-21-php-experience-2015/</link><pubDate>Sat, 21 Mar 2015 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/2015-03-21-php-experience-2015/</guid><description>Eventos</description><content:encoded><![CDATA[<p>PHP Experience 2015! Um dos maiores eventos sobre PHP da América Latina, contando com a presença de mais de 1000 programadores.</p>
<p>Saudações. Venho compartilhar com vocês mais uma edição do evento, o qual tive o privilégio de ir no ano passado (interconphp.imasters.com.br).</p>
<p><strong>PHP Experience 2015!</strong> Um dos maiores eventos sobre PHP da América Latina, contará com a presença de mais de 1000 programadores.</p>
<p>A edição desse ano ocorrerá no dia 25 de abril, sábado, das 8h às 18h30min. O valor para participar é R$ 149,00. Este valor só vale até o dia 22 de abril, então corra e garanta a sua vaga! O local será no Hotel Renaissance, na Alameda Santos, em São Paulo.</p>
<p>Segue uma série de vídeos do <strong>InterCon PHP 2014</strong>:</p>
<iframe width="560" height="310" src="https://www.youtube.com/embed/tdvqVIFBBE8" frameborder="0" allowfullscreen></iframe>
<p>PS: O evento foi sensacional. <a href="http://imasters.com.br/noticia/php-experience-acontece-hoje-em-sao-paulo/">Confira aqui</a>.</p>
]]></content:encoded><category>Eventos</category></item><item><title>Série de Hangouts sobre OOD</title><link>https://rifeli.dev/blog/2015-03-20-serie-de-hangouts-sobre-ood-principles-solid/</link><pubDate>Fri, 20 Mar 2015 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/blog/2015-03-20-serie-de-hangouts-sobre-ood-principles-solid/</guid><description>Quando o assunto Orientação a Objetos (paradigma orientado a objetos) é abordado, é imprescindível falarmos sobre OOD (object-oriented design). Temos o SOLID na aplicação de Padrões de Projetos.</description><content:encoded><![CDATA[<p>Quando o assunto <strong>Orientação a Objetos (paradigma orientado a objetos)</strong> é abordado, é imprescindível falarmos sobre <strong>OOD (object-oriented design)</strong>. Temos o SOLID na aplicação de Padrões de Projetos.</p>
<p>Descrevendo a sigla temos:</p>
<ol>
<li>
<p>[S]ingle Responsability Principle;</p>
</li>
<li>
<p>[O]pen/Closed Principle;</p>
</li>
<li>
<p>[L]iskov Substitution Principle;</p>
</li>
<li>
<p>[I]nterface Segregation Principle;</p>
</li>
<li>
<p>[D]ependency Inversion Principle.</p>
</li>
</ol>
<p>Segue uma série de hangouts gravados pelo <strong>PHP Brasil</strong> para melhor aprofundamento:</p>
<iframe width="560" height="310" src="https://www.youtube.com/embed/ea6d1Pf8bOE" frameborder="0" allowfullscreen></iframe>]]></content:encoded><category>CleanCode</category></item><item><title>it's sad to say goodbye</title><link>https://rifeli.dev/newsletter/unsubscriber/</link><pubDate>Thu, 05 Jan 1989 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/newsletter/unsubscriber/</guid><description>
É triste dar adeus, mas entendo que no momento não estamos na mesma sintonia, sinta-se livre para voltar a qualquer momento, será ótimo compartilhar o que estou estudando com você.</description><content:encoded><![CDATA[<p><img src="https://media.giphy.com/media/ISOckXUybVfQ4/giphy.gif#center" alt="via SpongeBob SquarePants on GIPHY"></p>
<p>É triste dar adeus, mas entendo que no momento não estamos na mesma sintonia, sinta-se livre para voltar a qualquer momento, será ótimo compartilhar o que estou estudando com você.</p>
<p>Caso queira voltar fazer parte da nossa newsletter tem o formulário aí na parte superior.</p>
<h2 id="en">EN</h2>
<p>It is sad to say goodbye, but I understand that at the moment we are not on the same page, feel free to come back anytime, it will be great to share what I am studying with you.</p>
<p>If you want to be part of our newsletter again, there is a form at the top.</p>
]]></content:encoded><category>newsletter</category><category>goodbye</category><category>bad bye</category><category>avelino</category><category>open source</category><category>thiago avelino</category></item><item><title>Welcome to newsletter</title><link>https://rifeli.dev/newsletter/welcome/</link><pubDate>Thu, 05 Jan 1989 00:00:00 +0000</pubDate><guid isPermaLink="true">https://rifeli.dev/newsletter/welcome/</guid><description>
Olá pessoa, seja bem-vinda a minha newsletter, será um prazer compartilhar minhas anotações com vocês.</description><content:encoded><![CDATA[<p><img src="https://media.giphy.com/media/3Hw2z62ewK4rm/giphy.gif#center" alt="via GIPHY"></p>
<p>Olá pessoa, seja bem-vinda a minha newsletter, será um prazer compartilhar minhas anotações com vocês.</p>
<p>O que você pode esperar por aqui?</p>
<ul>
<li>anotações das notícias que leio;</li>
<li>anotações do que tenho estudado atualmente, tenho atualmente estudado gestão de time de engenharia e como introduzir pessoas com zero de experiência em engenharia de software no nosso mundo;</li>
<li>o que tenho feito no mundo open source e quais projetos estou envolvido atualmente.</li>
</ul>
<p>Tenho habito de fazer anotações de tudo que estudo/leio, a diferença que farei isso com você publicamente.</p>
<h2 id="en">EN</h2>
<p>Hi people, welcome to my newsletter, it will be a pleasure to share my notes with you.</p>
<p>What can you expect here?</p>
<ul>
<li>notes on the news I read;</li>
<li>notes on what I&rsquo;m currently studying, I&rsquo;m currently studying engineering team management and how to introduce people with zero software engineering experience into our world;</li>
<li>what I have been doing in the open source world and what projects I am currently involved in.</li>
</ul>
<p>I have a habit of making notes of everything I study/read, the difference is that I will do this with you publicly.</p>
]]></content:encoded><category>newsletter</category><category>welcome</category><category>hello</category><category>avelino</category><category>open source</category><category>thiago avelino</category></item></channel></rss>