<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Frete.com Tech - Medium]]></title>
        <description><![CDATA[A Frete.com é formada pelas marcas Fretebras, maior plataforma de fretes rodoviários da América Latina, Intersite, sistemas de gerenciamento de transporte, e Cargo X, empresa que oferece serviços operacionais para transportadoras. - Medium]]></description>
        <link>https://medium.com/fretecom-tech?source=rss----a43f9b9429a---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>Frete.com Tech - Medium</title>
            <link>https://medium.com/fretecom-tech?source=rss----a43f9b9429a---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Fri, 13 Nov 2026 19:38:35 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/fretecom-tech" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Herdr: a solução para gerenciar vários agentes de IA sem perder o controle]]></title>
            <link>https://medium.com/fretecom-tech/herdr-a-solu%C3%A7%C3%A3o-para-gerenciar-v%C3%A1rios-agentes-de-ia-sem-perder-o-controle-08abf89fb88b?source=rss----a43f9b9429a---4</link>
            <guid isPermaLink="false">https://medium.com/p/08abf89fb88b</guid>
            <category><![CDATA[artificial-intelligence]]></category>
            <dc:creator><![CDATA[João Roberto da Paixão]]></dc:creator>
            <pubDate>Sun, 19 Jul 2026 23:56:47 GMT</pubDate>
            <atom:updated>2026-07-19T23:56:45.698Z</atom:updated>
            <content:encoded><![CDATA[<h4>Veja quem está trabalhando, quem terminou e quem precisa de você — sem acompanhar cada terminal de perto.</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Mk2nzFsylbReEXQEHjlPNQ.png" /></figure><p>Você deixa um agente investigando um bug, põe um segundo para mapear os testes, um terceiro para revisar a documentação e ainda sobe a aplicação e os logs em painéis separados.</p><p>Poucos minutos depois, começa a saga:</p><ul><li>Abre o primeiro terminal — nada mudou;</li><li>Abre o segundo — o agente terminou há três minutos e você não viu;</li><li>Abre o terceiro — ele está parado desde o início, esperando uma aprovação sua.</li><li>Etc…</li></ul><p>Rodar vários agentes de IA é fácil, o difícil é saber qual deles precisa da sua atenção no momento! O tempo que você gasta descobrindo, terminal a terminal, qual deles precisa de você agora é uma perca de produtividade. Cada verificação é uma troca de contexto — e a maioria não encontra nada.</p><p>O <a href="https://herdr.dev/">Herdr</a> ataca exatamente essa parte. Ele mantém agentes e outros processos em uma interface persistente e concentra, em uma barra lateral, o estado de cada um: quem está trabalhando, quem terminou e quem depende de uma decisão sua.</p><p>Ele não distribui tarefas, não evita conflitos no Git e não substitui <em>Codex</em>, <em>Claude Code</em> ou qualquer outro agente. Seu papel é organizar e tornar observável o ambiente onde esses agentes trabalham — para que rodar cinco não seja mais caótico do que rodar um.</p><p>Neste guia você percorre tudo na prática: sobe vários agentes em paralelo, usa os estados como fila de atenção, age só onde é necessário, sai sem interromper nada e volta para a mesma sessão. No fim, um bloco de dicas para escalar isso sem se perder.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*XqSigW9DcZa5_WnysLHAaw.gif" /></figure><h4>Um mapa rápido antes de começar</h4><p>O <strong>Herdr</strong> tem quatro níveis. Vale fixá-los agora, porque os passos seguintes usam esses termos:</p><ul><li><strong>Sessão:</strong> o ambiente persistente mantido pelo servidor do Herdr.</li><li><strong>Workspace:</strong> o espaço de um projeto, repositório, worktree ou investigação.</li><li><strong>Aba:</strong> uma separação de contexto dentro do workspace.</li><li><strong>Painel:</strong> um terminal real, onde roda um agente ou qualquer outro comando.</li></ul><p>Guardadas essas quatro palavras, o resto do fluxo é intuitivo.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*XscUJ1AaQ4gdBVrX8L2ZlA.png" /></figure><h4>O que muda em relação a tmux ou Zellij?</h4><p><a href="https://tmux.app/"><em>tmux</em></a> e <a href="https://zellij.dev/"><em>Zellij</em></a> resolvem muito bem a persistência de terminais: painéis, processos vivos, detach e reattach. O <strong>Herdr</strong> parte da mesma base, mas trata alguns desses processos como o que eles são — agentes de IA — e mostra o estado de cada um.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*I7jAl6Wo8vWvyP8lKEHOMw.png" /></figure><p>A diferença cabe em uma frase: <em>tmux</em> e <em>Zellij</em> mantêm os terminais vivos; o <strong>Herdr</strong> também diz quais deles pedem uma ação sua.</p><h4>Antes de começar</h4><p>Você precisa de um terminal, um projeto local e pelo menos um agente de programação instalado — <em>Codex</em> ou <em>Claude Code</em>, por exemplo. Dá para subir duas instâncias do mesmo agente; não é preciso instalar ferramentas diferentes para experimentar o fluxo.</p><p>Git não é necessário para o primeiro teste. Ele passa a importar quando dois ou mais agentes podem alterar o projeto ao mesmo tempo.</p><h3>Mãos à obra: vários agentes em paralelo</h3><h4>1. Instale e abra a ferramenta</h4><p>No Linux ou macOS, use o instalador oficial:</p><pre>curl -fsSL https://herdr.dev/install.sh | sh</pre><p>Também é possível instalar com Homebrew ou mise:</p><pre>brew install herdr</pre><pre>mise use -g herdr</pre><p>No Windows, o suporte nativo ainda está em beta e utiliza o canal preview:</p><pre>powershell -ExecutionPolicy Bypass -c &quot;irm https://herdr.dev/install.ps1 | iex&quot;</pre><p>Abra um novo terminal se o comando ainda não estiver no PATH. Depois, entre no projeto e inicie o <strong>Herdr</strong>:</p><pre>cd ~/projetos/meu-projeto<br>herdr</pre><p>Em uma sessão vazia, o primeiro <em>workspace</em> é criado automaticamente.</p><blockquote><em>Uma nota sobre atalhos:quase todos começam com o prefixo </em><em>Ctrl+B. Você pressiona </em><em>Ctrl+B, solta as teclas e então pressiona a próxima tecla. Daqui em diante, escrevo apenas &quot;</em><em>Ctrl+B depois </em><em>V&quot;.</em></blockquote><p>Antes de subir os agentes, vale um aviso sobre flexibilidade: o <strong>Herdr</strong> funciona sem nenhum arquivo de configuração, mas deixa você adaptar quase tudo quando quiser:</p><ul><li>temas, cores e alternância entre modos claro e escuro;</li><li>atalhos e comandos personalizados;</li><li>largura, conteúdo e ordenação da barra lateral;</li><li>shell e diretório inicial de novos painéis;</li><li>diretório usado para criar worktrees;</li><li>comportamento das conexões remotas.</li></ul><p>Você pode abrir as configurações direto pela interface <em>(</em><em>Ctrl+B depois </em><em>S)</em> ou ver todas as opções em texto com herdr --default-config e editar ~/.config/herdr/config.toml.</p><p>Depois de editar o arquivo, aplique a maioria das mudanças sem reiniciar os painéis:</p><pre>herdr server reload-config</pre><p>Nada disso é necessário para começar — volte a esta seção quando quiser afinar a experiência.</p><h4>2. Suba os agentes</h4><p>No painel aberto, execute o agente como faria em qualquer terminal:</p><pre>claude</pre><p>Dê a ele uma tarefa segura e somente de leitura:</p><pre>Sem alterar arquivos ou executar comandos destrutivos, descreva a<br>estrutura deste projeto e explique como a aplicação é iniciada localmente.</pre><p>Enquanto ele processa, a barra lateral o mostra como working.</p><p>Divida o painel à direita com Ctrl+B depois V (<em>ou clique com o botão direito no painel)</em>. No novo painel, suba outra instância:</p><pre>claude</pre><p>Dê a ela uma tarefa diferente, ainda sem escrita:</p><pre>Sem alterar arquivos, aponte onde estão os testes, como executá-los<br>e quais partes importantes parecem sem cobertura.</pre><p>Repita mais uma ou duas vezes — um agente para revisar a documentação, outro para propor um refactor, todos em modo somente leitura por enquanto:</p><pre>Sem alterar arquivos, liste inconsistências e trechos desatualizados<br>na documentação do projeto.</pre><p>Agora há três ou quatro agentes no mesmo <em>workspace</em>. Este é o momento em que, sem o <strong>Herdr</strong>, começaria a saga de abrir um terminal atrás do outro. Com ele, você olha uma coluna só!</p><h4>3. Use os estados como fila de atenção</h4><p>A barra lateral não é uma lista de terminais; é uma fila de prioridade. Cada agente carrega um estado:</p><ul><li><strong>blocked</strong>: Precisa de resposta, aprovação ou decisão. Abra-o primeiro.</li><li><strong>done</strong>: Terminou e você ainda não viu o resultado. Revise a saída e as possíveis alterações.</li><li><strong>working</strong>: Está executando a tarefa. Normalmente, deixe-o trabalhar.</li><li><strong>idle</strong>: Está aguardando, ou você já viu a conclusão.Retome quando fizer sentido.</li><li><strong>unknown</strong>: O <strong>Herdr</strong> não classificou o estado com segurança. Verifique o painel diretamente.</li></ul><p>Os estados não seguem uma ordem fixa: um agente pode ir direto para done; outro pode parar no meio para pedir autorização ou esclarecer o escopo. Por isso, uma regra resolve quase tudo:</p><blockquote><strong><em>Primeiro </em></strong><strong><em>blocked, depois </em></strong><strong><em>done. O resto raramente precisa de você.</em></strong></blockquote><p>Com quatro agentes, a fila de atenção se parece com isto:</p><pre>Agente A · bug        working ───────────────► done  ── revisar<br>Agente B · testes     working ─► blocked ─► você responde ─► working<br>Agente C · docs       working ─────────────────────────► done<br>Agente D · refactor   working ───────────────────────────────►<br>Você                  sobe todos ─► lê a sidebar ─► trata blocked, depois done</pre><p>Repare que você nunca abriu os agentes A e D enquanto trabalhavam: a coluna já dizia que não precisavam de você. Esse é o ganho central — o Herdr não decide por você, mas derruba o custo de descobrir onde a decisão é necessária.</p><h4><strong>Deixe o Herdr chamar você de volta</strong></h4><p>O recurso que mais mudou meu fluxo foi poder largar os agentes trabalhando e ir fazer outra coisa. Quando um deles termina ou trava, o <strong>Herdr</strong> toca um som ou exibe uma notificação — não preciso manter o terminal em primeiro plano nem revisar os painéis o tempo todo.</p><p>As notificações visuais têm quatro modos de entrega:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/987/1*SREVZlPEVPrmZvpEJdt8vA.png" /><figcaption>&quot;Ctrl+B” depois &quot;S&quot;</figcaption></figure><p>Para receber uma notificação do sistema quando um agente em segundo plano terminar ou pedir atenção, ajuste as configurações.</p><p>Há duas formas de fazer isso: pela interface, com Ctrl+B depois S, ou editando o arquivo ~/.config/herdr/config.toml:</p><pre>[ui.toast]<br>delivery = &quot;system&quot;<br>delay_seconds = 2</pre><pre>[ui.sound]<br>enabled = true</pre><p>O atraso evita alarme para mudanças de estado muito rápidas: o <strong>Herdr</strong> só notifica se o agente ainda estiver no mesmo estado quando o intervalo terminar.</p><p>Juntas, sidebar e notificação fecham o ciclo de atenção: a barra lateral mostra onde agir quando você está no <strong>Herdr</strong>; a notificação chama você de volta quando está em outra coisa.</p><h3>Dicas de produtividade para rodar muitos agentes com ainda mais produtividade</h3><p>O básico acima escala bem, mas alguns hábitos fazem diferença quando o número de agentes cresce:</p><ol><li><strong>Trabalhe pela fila.</strong> Só abra blocked e done. Enquanto um agente estiver working, confie na coluna e siga no seu painel.</li><li><strong>Ordene a barra lateral por estado.</strong> Deixe blocked e done no topo — eles sobem sozinhos para onde seu olho já está. (Rode herdr --default-config para ver a chave exata.)</li><li><strong>Nomeie abas e painéis.</strong> “testes”, “docs” e “refactor” em uma lista de oito agentes valem muito mais que oito claude idênticos. <em>(clique com botão direito em cima e vá na opção “rename”)</em></li><li><strong>Mantenha servidor, testes e logs ao lado.</strong> Os painéis são terminais reais: deixe a aplicação, os testes e os logs em abas próprias, junto dos agentes, e você acompanha tudo sem trocar de janela.</li><li><strong>Dê escopo e permissões na própria tarefa.</strong> Quanto mais claro o pedido e quanto mais cedo você autoriza o que é seguro, menos vezes o agente volta para blocked no meio do caminho.</li><li><strong>Confie na notificação para trocar de contexto de verdade.</strong> Largue os agentes, vá para outra atividade e deixe o <strong>Herdr</strong> chamar você.</li><li><strong>Um escritor por worktree.</strong> É a regra que mantém o paralelismo seguro quando os agentes passam a alterar arquivos — detalhada logo adiante.</li></ol><h3>Indo além do básico</h3><p>Os recursos a seguir entram em cena quando você passa de “experimentando” para “usando no dia a dia”: agentes que escrevem código, trabalho em máquina remota, restauração de sessões e automação.</p><p>Pule à vontade se ainda está conhecendo a ferramenta.</p><h4>Uma worktree para cada escritor</h4><p>O <strong>Herdr</strong> facilita subir vários agentes, mas não torna seguro que todos alterem a mesma árvore de trabalho. Mesmo com arquivos diferentes, eles podem executar formatadores, atualizar dependências, gerar <em>migrations</em>, mexer em <em>snapshots</em> ou manipular o index do Git.</p><p>Adote uma regra simples:</p><blockquote><strong><em>Uma </em>worktree<em> pode ter vários agentes somente de leitura, mas no máximo um agente com permissão de escrita.</em></strong></blockquote><p>Para criar uma <em>worktree</em> manualmente:</p><pre>git worktree add ../meu-projeto-tarefa-a -b tarefa/a main</pre><p>Isso cria outro diretório, ligado ao mesmo repositório, com branch e arquivos independentes. No <strong>Herdr</strong>, Ctrl+B depois Shift+G inicia o fluxo de criação de uma worktree a partir do workspace selecionado.</p><p>Um fluxo seguro é:</p><ol><li>O agente A investiga e implementa uma mudança na worktree dele.</li><li>Servidor e testes rodam dentro dessa mesma worktree.</li><li>Havendo um diff, outro agente pode revisá-lo em modo somente leitura.</li><li>Uma segunda mudança independente ganha outra worktree e outra branch.</li><li>Você revisa os resultados e decide o que integrar.</li></ol><p><em>Worktrees</em> isolam arquivos e branches, não responsabilidades. Antes de soltar dois escritores, você ainda precisa explicar, em uma frase, a fronteira entre as tarefas.</p><h4>Trabalho remoto por SSH</h4><p>Execute o <strong>Herdr</strong> na máquina onde estão o código, os processos e as credenciais:</p><pre>ssh usuario@servidor<br>cd /caminho/do/projeto<br>herdr</pre><p>Faça detach com Ctrl+B depois Q, encerre a conexão SSH e retorne quando quiser:</p><pre>ssh usuario@servidor<br>herdr</pre><p>Também é possível conectar um cliente local direto a um servidor remoto:</p><pre>herdr --remote &lt;host&gt;</pre><p>O modelo serve bem a tarefas demoradas: os processos seguem na máquina remota mesmo quando seu computador local se desconecta.</p><h4>Automação e diagnóstico</h4><p>A personalização você já viu na instalação. Faltam dois recursos mais avançados — deixar um agente operar o próprio <strong>Herdr</strong> e diagnosticar estados classificados errado:</p><p>Para deixar um agente controlar painéis, ler saídas e aguardar processos, instale a skill oficial em agentes compatíveis:</p><pre>npx skills add ogulcancelik/herdr --skill herdr -g</pre><p>Domine primeiro o ciclo <em>agente → estado → decisão → detach → retorno</em>. A automação deve ampliar um fluxo que você já compreende, não substituí-lo.</p><p>Se um agente aparecer com o estado errado, confira a integração e peça ao <strong>Herdr</strong> que explique a classificação:</p><pre>herdr integration status<br>herdr agent explain &lt;target&gt;</pre><p>Se necessário, atualize as regras de detecção com herdr server update-agent-manifests. O estado blocked é deliberadamente conservador, e uma interface nova pode aparecer como idle até que seu padrão seja reconhecido.</p><h3>Quando o Herdr vale a pena?</h3><p>Se você usa um único agente e acompanha cada execução até o fim, seu terminal atual provavelmente basta.</p><p>O <strong>Herdr</strong> passa a fazer diferença quando:</p><ul><li>dois ou mais agentes trabalham ao mesmo tempo;</li><li>agentes, servidor, testes, logs (etc…) convivem juntos no seu workspace;</li><li>você perde tempo verificando abas que não mudaram;</li><li>tarefas demoradas precisam continuar depois de fechar o terminal;</li><li>o trabalho acontece em uma máquina remota por SSH.</li></ul><p>Para avaliar a ferramenta, faça um teste pequeno:</p><ul><li>abra um projeto;</li><li>suba dois ou três agentes somente de leitura;</li><li>acompanhe blocked e done pela barra lateral;</li><li>confirme se você sabe, de imediato, qual agente precisa de atenção.</li></ul><p>Se a resposta estiver à vista sem percorrer vários terminais, o <strong>Herdr</strong> já resolveu sua dor principal!</p><h4>Fontes oficiais</h4><ul><li><a href="https://herdr.dev/">Site do Herdr</a></li><li><a href="https://herdr.dev/docs/">Documentação</a></li><li><a href="https://herdr.dev/docs/install/">Instalação</a></li><li><a href="https://herdr.dev/docs/quick-start/">Quick start</a></li><li><a href="https://herdr.dev/docs/concepts/">Conceitos e estados dos agentes</a></li><li><a href="https://herdr.dev/docs/agents/">Agentes e detecção de estados</a></li><li><a href="https://herdr.dev/compare/">Comparação com outras ferramentas</a></li><li><a href="https://herdr.dev/docs/integrations/">Integrações</a></li><li><a href="https://herdr.dev/docs/session-state/">Estado e restauração de sessões</a></li><li><a href="https://herdr.dev/docs/how-to-work/">Trabalho remoto</a></li><li><a href="https://herdr.dev/docs/configuration/">Configuração</a></li><li><a href="https://herdr.dev/docs/config-reference/">Referência completa de configuração</a></li><li><a href="https://herdr.dev/docs/agent-skill/">Skill para agentes</a></li><li><a href="https://herdr.dev/docs/troubleshooting/">Solução de problemas</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=08abf89fb88b" width="1" height="1" alt=""><hr><p><a href="https://medium.com/fretecom-tech/herdr-a-solu%C3%A7%C3%A3o-para-gerenciar-v%C3%A1rios-agentes-de-ia-sem-perder-o-controle-08abf89fb88b">Herdr: a solução para gerenciar vários agentes de IA sem perder o controle</a> was originally published in <a href="https://medium.com/fretecom-tech">Frete.com Tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Injeção de Dependências com Koin no Kotlin Multiplatform (KMP)]]></title>
            <link>https://medium.com/fretecom-tech/inje%C3%A7%C3%A3o-de-depend%C3%AAncias-com-koin-no-kotlin-multiplatform-kmp-d3eda45249e6?source=rss----a43f9b9429a---4</link>
            <guid isPermaLink="false">https://medium.com/p/d3eda45249e6</guid>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[kmp]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[kotlin-multiplatform]]></category>
            <dc:creator><![CDATA[Raphael de Albuquerque Azevedo]]></dc:creator>
            <pubDate>Thu, 23 Oct 2025 16:47:52 GMT</pubDate>
            <atom:updated>2025-10-23T16:47:49.992Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*m0o7-UTxjR90ZRaI_TNPnA.jpeg" /><figcaption>Imagem gerada por IA</figcaption></figure><h3>1. Introdução</h3><p>Quando falamos em <strong>Kotlin Multiplatform (KMP)</strong>, um dos desafios é organizar o código de forma que possamos compartilhar lógica entre diferentes plataformas (Android, IOS).<br>Nesse cenário, o uso de <strong>Injeção de Dependência (DI)</strong> ajuda a manter o projeto escalável, testável e limpo.</p><p>Neste artigo, vou mostrar como utilizei o <strong>Koin</strong> em um app simples de <strong>lista de tarefas</strong>, desenvolvido em KMP, para gerenciar dependências como repositories, usecases e viewmodels.</p><h3>2. O que é Injeção de Dependência?</h3><p>Injeção de Dependência é um padrão de projeto que delega a criação das dependências para um componente externo, em vez de a própria classe instanciá-las. Isso aplica o princípio da Inversão de Dependência, garantindo que módulos dependam de abstrações e não de implementações concretas.</p><p><strong>Benefícios:</strong></p><p><strong>— Desacoplamento:</strong> Classes conhecem interfaces/abstrações, não implementações concretas.</p><p><strong>— Testabilidade:</strong> Fica fácil trocar uma implementação real por um mock/fake nos testes.</p><p><strong>— Composição centralizada:</strong> Criação e configuração de objetos fica em um único lugar (<em>modules</em>).</p><p><strong>— Reuso e manutenção:</strong> Mudar implementações (ex: trocar cliente HTTP) sem mexer no código consumidor.</p><p><strong>— Gerência de ciclo de vida:</strong> O container controla <em>singletons</em>, <em>factories</em> e <em>scopeds</em>:</p><ul><li><strong>Singleton →</strong> Uma instância global criada apenas uma vez.</li><li><strong>Factory →</strong> Uma nova instância a cada uso.</li><li><strong>Scoped →</strong> Uma instância por ciclo de vida específico (sessão, activity, etc.).</li></ul><h3>3. Como o koin funciona?</h3><p>1) Módulos e definições</p><ul><li><strong>Módulo</strong> (module {...}) é onde você declara <em>definitions</em>: single {...}, factory {...}, scoped {...}, viewModel {...} etc.</li><li>Cada definição descreve <strong>como construir</strong> um tipo/serviço. O Koin constrói o grafo lendo os módulos na inicialização.</li><li>Dentro das definições você pode usar get() para solicitar ao Koin uma instância já registrada no grafo, permitindo compor objetos a partir de suas dependências sem criá-los manualmente.</li></ul><p>Exemplo :</p><pre>val appModule = module {<br><br>    factory { AddTaskUseCase(customKoin.koin.get&lt;TaskRepository&gt;()) }<br>    factory { RemoveTaskUseCase(customKoin.koin.get&lt;TaskRepository&gt;()) }<br>    factory { GetTasksUseCase(customKoin.koin.get&lt;TaskRepository&gt;()) }<br><br>    viewModel&lt;TaskViewModel&gt; {<br>        TaskViewModel(<br>            addTaskUseCase = get(),<br>            removeTaskUseCase = get(),<br>            getTasksUseCase = get()<br>        )<br>    }<br>}</pre><p>2) Inicialização: startKoin / KoinApplication / GlobalContext</p><p>No Android, a forma mais comum de inicializar o Koin é dentro da classe que estende Application. Assim, o container de DI(<em>Dependency Injection</em>) é configurado logo no início do ciclo de vida do app e fica disponível em toda a aplicação.</p><p>Exemplo:</p><pre>class TaskApp : Application() {    <br>    override fun onCreate() {        <br>        super.onCreate() <br>        startKoin { <br>            androidContext(this@TaskApp)            <br>            modules(appModule)        <br>        }<br>    } <br>}</pre><p><strong>O que acontece aqui</strong></p><ul><li>startKoin{...} inicializa o container de <strong>Injeção de Dependências (Dependency Injection)</strong> e registra o contexto global do Koin via GlobalContext.<br>A partir desse ponto que o Koin passa a gerenciar e fornecer as instâncias definidas nos seus módulos.</li><li>androidContext(this@TaskApp) esta linha informa ao Koin qual é o <strong>Context global da aplicação Android</strong>.</li><li><strong>Integração com ViewModel do AndroidX</strong>, já que o Koin precisa conhecer o ciclo de vida e o contexto do app.</li><li>modules(appModule) carrega as definições de dependências que você declarou no módulo appModule.</li></ul><p><strong>Em resumo:</strong> Sem o androidContext, o Koin enxerga apenas as definições puras de Kotlin. Ao fornecê-lo, você conecta o container de DI ao ambiente Android, habilitando recursos extras como: módulos Android (androidModule), integração com viewModel{...}e a possibilidade de injetar classes que dependem diretamente do framework Android.</p><p>No <strong>IOS (Kotlin Multiplatform)</strong>, não há Application, então o Koin é inicializado manualmente dentro do código compartilhado (geralmente em initKoin()), e essa função é chamada a partir do código Swift no início da execução do app.<br> Assim, o container também fica disponível para o restante da aplicação IOS.</p><p><strong>Exemplo (Kotlin — módulo compartilhado — Helper.kt):</strong></p><pre>fun initKoin(){<br>    startKoin {<br>        modules(appModule)<br>    }<br>}</pre><p><strong>Exemplo (Swift — iOS App):</strong></p><pre>@main<br>struct iOSApp: App {<br>    init() {<br>        HelperKt.doInitKoin()<br>    }<br><br>    var body: some Scene {<br>        WindowGroup {<br>            ContentView()<br>        }<br>    }<br>}</pre><p>Depois da inicialização, qualquer classe que implemente KoinComponent pode usar:</p><pre>val repo: TaskRepository by inject()</pre><p>ou diretamente:</p><pre>val repo = get&lt;TaskRepository&gt;()</pre><p>3) Alternativa: contextos isolados com KoinApplication</p><p>Normalmente, quando você chama startKoin { ... }, o Koin cria um <strong>contexto global</strong> de injeção de dependências (GlobalContext). Isso funciona bem para apps comuns, mas em alguns cenários você pode precisar <strong>evitar esse contexto global</strong>:</p><ul><li><strong>Bibliotecas/SDKs</strong>: se sua lib usa Koin internamente, não é uma boa prática registrar módulos direto no contexto global do app, pois pode gerar conflitos de nomes/tipos com o app principal.</li><li><strong>Múltiplos contextos separados</strong>: em casos mais avançados, você pode querer rodar dois contêineres de DI(<em>dependency injection</em>) distintos (ex.: testar dois ambientes diferentes, “mock” vs “real”, ou isolar dependências de um módulo específico).</li></ul><p>Nesses casos, em vez de usar startKoin, você pode criar um <strong>contexto local</strong> com koinApplication { ... }.</p><p>Exemplo:</p><pre>val libraryModule = module {<br>    single&lt;MyService&gt; { MyServiceImpl() }<br>}<br>// Cria uma instância local e independente do Koin,<br>// útil para uso em bibliotecas sem depender do contexto global.<br>val customKoin = koinApplication {<br>    modules(libraryModule)<br>}<br>val service = customKoin.koin.get&lt;MyService&gt;()</pre><p>O que este código faz:</p><ul><li>koinApplication { ... } cria uma instância <strong>independente</strong> de KoinApplication.</li><li>Você acessa as dependências usando customKoin.koin.get&lt;T&gt;() em vez de get() global.</li><li>Esse contêiner não interfere no startKoin do app principal — ou seja, app e lib podem coexistir sem conflitos.</li></ul><p>4) E como seria a injeção de dependencia do repository como singleton sem o uso de frameworks?</p><pre>object ServiceLocator {  <br>    val taskRepository: TaskRepository by lazy {  <br>        TaskRepositoryImpl()  <br>    }<br>    <br>    fun provideTaskViewModel(): TaskViewModel {  <br>        return TaskViewModel(repository = taskRepository)  <br>    }  <br>}</pre><p>Como seria usado:</p><pre>val viewModel = ServiceLocator.provideTaskViewModel()</pre><p>O que este código faz:</p><ul><li>ServiceLocator funciona como um <strong>mini container</strong> criado por você.</li><li>by lazy garante que TaskRepositoryImpl() só será instanciado uma vez (similar com o single do Koin).</li><li>Quando precisar de um TaskViewModel, você chama provideTaskViewModel(), que passa o repository correto.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*PTJJtu3b9vormHvaarhG4A.png" /></figure><p>5) Qual é a principal diferença prática entre factory e single?</p><ul><li>Factory</li></ul><blockquote>O factory cria sempre um novo repositório a cada navegação, então os registros anteriores não são mantidos na memória — ao voltar, a lista de tarefas está zerada.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/940/1*RnFGD74ciNil0bnZh5ooZQ.gif" /></figure><p>No código:</p><pre>val appModule = module {<br>    factory&lt;TaskRepository&gt; { TaskRepositoryImpl() }<br><br>    viewModel&lt;TaskViewModel&gt; {<br>        TaskViewModel(repository = get())<br>    }<br>}</pre><ul><li>Single</li></ul><blockquote>O single garante que o TaskRepository seja compartilhado entre telas, mantendo as tarefas vivas na memória durante toda a navegação — diferente do factory, onde os dados se perdem a cada nova instância.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/940/1*7roFKU3oOvcPj_TbpTBrLg.gif" /></figure><p>No código:</p><pre>val appModule = module {<br>    single&lt;TaskRepository&gt; { TaskRepositoryImpl() }<br>    viewModel&lt;TaskViewModel&gt; {<br>        TaskViewModel(repository = get())<br>    }<br>}</pre><p>6) Teste unitários</p><ul><li>Código de produção (com Koin)</li></ul><pre>interface TaskRepository {<br>    fun all(): List&lt;Task&gt; <br>}</pre><pre>class TaskRepositoryImpl : TaskRepository {  <br>    private val items = mutableListOf&lt;Task&gt;()<br>    override fun all(): List&lt;Task&gt; = items.toList()<br>}</pre><pre>class TaskViewModel constructor(<br>  private val repository: TaskRepository<br>): ViewModel() {<br>    private val scope = CoroutineScope(Dispatchers.Default)<br>    private val _tasks = MutableStateFlow&lt;List&lt;Task&gt;&gt;(emptyList())<br>    val tasks: StateFlow&lt;List&lt;Task&gt;&gt; get() = _tasks<br><br>    private fun loadTasks() {<br>        scope.launch {<br>            _tasks.value = repository.all()<br>        }<br>    }<br>}</pre><pre>val appModule = module {<br>    single&lt;TaskRepository&gt; { TaskRepositoryImpl() }<br>    <br>    viewModel&lt;TaskViewModel&gt; {<br>        TaskViewModel(repository = get())<br>    }<br>}</pre><ul><li>Teste com Koin</li></ul><p>Com Koin, você pode <strong>sobrescrever os módulos reais</strong> dentro do teste:</p><pre>class TaskViewModelTest : KoinTest {<br>    private val testModule = module(override = true) {<br>           single&lt;TaskRepository&gt; { FakeTaskRepository() }<br>    }<br>    <br>    @Before<br>    fun setup() {<br>        startKoin {<br>            modules(testModule)<br>        }<br>    }<br>    <br>    @After<br>    fun teardown() {<br>        stopKoin()<br>    }<br>    @Test<br>    fun `loadTasks deve retornar lista fake`() {<br>        val expectedList = listOf(&quot;Tarefa falsa 1&quot;, &quot;Tarefa falsa 2&quot;)<br>        val viewModel: TaskViewModel by inject()<br>        val result = viewModel.loadTasks()<br>        assertEquals(expectedList, result)<br>    }<br>}</pre><ul><li>Teste sem Koin (injeção manual)</li></ul><p>Se você não tivesse Koin, teria que <strong>criar manualmente o ViewModel com uma fake/mock</strong></p><pre>class FakeTaskRepository : TaskRepository {<br>    override fun all() = listOf(&quot;Tarefa falsa 1&quot;, &quot;Tarefa falsa 2&quot;)    <br>}</pre><pre>@Test<br>fun `loadTasks deve retornar lista fake`() {<br>    val expectedList = listOf(&quot;Tarefa falsa 1&quot;, &quot;Tarefa falsa 2&quot;)<br>    val fakeRepository = FakeTaskRepository()<br>    val viewModel = TaskViewModel(fakeRepository)<br>    val result = viewModel.loadTasks()<br>    assertEquals(expectedList, result)<br>}</pre><h3>4. Vantagens do Koin frente a outras bibliotecas de DI</h3><p><em>O Koin se destaca por várias razões no ecossistema Kotlin e KMP:</em></p><ul><li><strong>Sintaxe Kotlin-first e DSL intuitiva</strong> — o Koin foi feito para Kotlin, usando sua linguagem de forma fluida, sem depender de muitas anotações ou boilerplate.</li><li><strong>Sem geração de código obrigatória</strong> — diferentemente de frameworks como Hilt ou Dagger que dependem de processamento de anotação e arquivos gerados, o Koin resolve o grafo em tempo de execução, o que simplifica o setup e agiliza o desenvolvimento.</li><li><strong>Leveza e agilidade</strong> — Menos configuração, menos boilerplate e maior rapidez para começar a usar no projeto. Isso favorece a produtividade, especialmente em apps que vão evoluir ou que têm múltiplas plataformas.</li><li><strong>Compatível com Kotlin Multiplatform (KMP)</strong> — O Koin funciona bem em projetos multiplataforma, o que o torna mais adequado para um cenário onde você compartilha lógica entre Android e IOS.</li><li><strong>Flexibilidade para testes</strong> — A simplicidade dos módulos e do container torna mais fácil sobrescrever dependências em testes unitários ou configurar versões diferentes para diferentes ambientes.</li></ul><h3>5. Como seria na prática com KMP?</h3><p>1. Configuração</p><p>libs.versions.toml</p><pre>[versions]  <br>koin = &quot;4.1.1&quot;<br><br>[libraries]  <br>koin-core = { module = &quot;io.insert-koin:koin-core&quot;, version.ref = &quot;koin&quot; }  <br>koin-android = { module = &quot;io.insert-koin:koin-android&quot;, version.ref = &quot;koin&quot; }  <br>koin-androidx-compose = { module = &quot;io.insert-koin:koin-androidx-compose&quot;, version.ref = &quot;koin&quot; }</pre><p>2. Projeto simples</p><pre>interface TaskRepository {  <br>    fun add(todo: Task)<br>    fun remove(id: String)<br>    fun all(): List&lt;Task&gt; <br>}</pre><pre>class TaskRepositoryImpl : TaskRepository {  <br>    private val items = mutableListOf&lt;Task&gt;()<br>    override fun add(todo: Task) { items.add(todo) }<br>    override fun remove(id: String) { items.removeAll { it.id == id } }<br>    override fun all(): List&lt;Task&gt; = items.toList()<br>}</pre><pre>class TaskViewModel constructor(<br>  private val repository: TaskRepository<br>): ViewModel() {<br>    private val scope = CoroutineScope(Dispatchers.Default)<br>    private val _tasks = MutableStateFlow&lt;List&lt;Task&gt;&gt;(emptyList())<br>    val tasks: StateFlow&lt;List&lt;Task&gt;&gt; get() = _tasks<br>    <br>    init {<br>        loadTasks()<br>    }<br>    <br>    fun addTask(title: String) {<br>        scope.launch {<br>            repository.add(Task(randomString(), title))<br>            loadTasks()<br>        }<br>    }<br>    <br>    fun removeTask(id: String) {<br>        scope.launch {<br>            repository.remove(id)<br>            loadTasks()<br>        }<br>    }<br>    <br>    private fun loadTasks() {<br>        scope.launch {<br>            _tasks.value = repository.all()<br>        }<br>    }<br>}</pre><pre>val appModule = module {<br>    single&lt;TaskRepository&gt; { TaskRepositoryImpl() }<br>    viewModel&lt;TaskViewModel&gt; {<br>        TaskViewModel(repository = get())<br>    }<br>}</pre><p>Injeção de depência no Android</p><pre>class TaskApp : Application() {<br>    override fun onCreate() {<br>        super.onCreate()<br>        startKoin {<br>           androidContext(this@TaskApp)<br>           modules(appModule)<br>        }<br>    }<br>}</pre><p>Injeção de depência no IOS</p><p>helper.kt</p><pre>fun initKoin(){<br>    startKoin {<br>        modules(appModule)<br>    }<br>}</pre><p>iOSApp.swift</p><pre>@main<br>struct iOSApp: App {<br>    init() {<br>        HelperKt.doInitKoin()<br>    }<br>    var body: some Scene {<br>        WindowGroup {<br>            ContentView()<br>        }<br>    }<br>}</pre><p>3. Pontos relevantes</p><ul><li><strong>Mantenha sua lógica multiplataforma (commonMain) desacoplada da API do framework</strong>: preferível usar <em>constructor injection</em> em classes do commonMain para não acoplar o código à API do Koin.</li><li><strong>Inicialização</strong>: normalmente você declara os modules no commonMain (dependências comuns) e <em>inicia o Koin no código de plataforma</em> (Android, iOS), adicionando módulos específicos de plataforma (ex.: engines do Ktor, implementações nativas).</li><li><strong>Teste no commonMain</strong>: como seu commonMain fica desacoplado, fica fácil trocar módulos por test modules em testes de unidade multiplataforma.</li><li><strong>Limite o uso de </strong><strong>by inject() ou </strong><strong>KoinComponent</strong> ao código da camada extrema(UI por exemplo), não no core da lógica de negócio, dessa forma, o <strong>domínio permanece independente de frameworks</strong> e testável, enquanto o Koin atua apenas como um orquestrador na borda do sistema.</li><li><strong>Use qualifiers</strong> quando tiver múltiplas implementações do mesmo tipo (ex.: named(&quot;local&quot;) / named(&quot;remote&quot;)).</li><li><strong>Evite singletons mutáveis</strong> — prefira objetos imutáveis ou providers/factories para evitar problemas em Kotlin/Native.</li><li><strong>Teste com módulos de substituição</strong> (override) para trocar impls reais por fakes/mocks.</li></ul><p>4. Anti-patterns</p><ul><li><strong>Service Locator como padrão</strong>: chamar GlobalContext.get().get() em todo o lugar esconde dependências e dificulta testes.</li><li><strong>Exposição do container em camadas internas</strong>: não passe o container pelo modelo/domain.</li></ul><pre>class SaveTaskUseCase(private val container: Koin) {<br>    fun execute(task: Task) {<br>        val repository: TaskRepository = container.get()<br>        repository.save(task)<br>     }<br>}</pre><ul><li><strong>Singletons globais com estado</strong>: podem criar acoplamento e problemas de concorrência (especialmente em Kotlin/IOS Native).</li><li><strong>Misturar muitos papéis em um módulo</strong>: módulos inchados dificultam mocks em testes.</li></ul><h3>6. Estrutura do Projeto</h3><p>Abaixo está uma representação visual da <strong>estrutura de pastas do projeto KMP</strong> utilizado para demonstrar o uso do <strong>Koin</strong> na injeção de dependências.<br> A organização segue os princípios de <strong>Clean Architecture</strong>, separando claramente as camadas de <strong>data</strong>, <strong>domain</strong>, <strong>presentation</strong>, <strong>injection</strong> e <strong>navigation</strong>, dentro do módulo commonMain.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/708/1*vMQAafY5zd2YykxORK-Odg.png" /></figure><h3>7. Conclusão</h3><p>A Injeção de Dependência (DI) no Kotlin Multiplatform vai além de simplificar a criação de objetos: ela garante que sua base de código permaneça <strong>desacoplada, testável e escalável</strong> entre diferentes plataformas.</p><p>O Koin se destaca nesse contexto porque combina simplicidade com flexibilidade. No commonMain, podemos manter a lógica de negócio limpa, orientada a interfaces e livre de referências diretas ao framework.</p><p>Além disso, o Koin facilita a troca de implementações em testes, reduz o boilerplate em comparação com um ServiceLocator manual e oferece controle refinado de ciclo de vida (single, factory, scoped). Porém, é importante evitar anti-padrões como o uso excessivo do GlobalContext ou a criação de singletons mutáveis em Kotlin/Native.</p><p>Em resumo, a melhor prática no KMP é:</p><ul><li>manter o commonMain desacoplado e limpo;</li><li>inicializar e compor dependências nas plataformas;</li><li>usar o Koin como suporte estratégico para bootstrap, composição e testes.</li></ul><p>Assim, seu código ganha clareza, reuso e facilidade de manutenção em projetos multiplataforma de qualquer porte.</p><h3>8. Referências</h3><ul><li><a href="https://insert-koin.io/">Documentação oficial do Koin</a></li><li><a href="https://www.jetbrains.com/compose-multiplatform/">Compose Multiplatform Docs</a></li><li><a href="https://github.com/InsertKoinIO/koin">Koin no GitHub</a></li><li><a href="https://github.com/raphaeldealbuquerque/TodoListKMP">Exemplo de projeto com Koin + KMP + compose multiplatform (GitHub)</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d3eda45249e6" width="1" height="1" alt=""><hr><p><a href="https://medium.com/fretecom-tech/inje%C3%A7%C3%A3o-de-depend%C3%AAncias-com-koin-no-kotlin-multiplatform-kmp-d3eda45249e6">Injeção de Dependências com Koin no Kotlin Multiplatform (KMP)</a> was originally published in <a href="https://medium.com/fretecom-tech">Frete.com Tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A jornada da Fretebras com Kotlin Multiplatform (KMP): desafios e aprendizados]]></title>
            <link>https://medium.com/fretecom-tech/a-jornada-da-fretebras-com-kotlin-multiplatform-kmp-desafios-e-aprendizados-3501dffc5c2a?source=rss----a43f9b9429a---4</link>
            <guid isPermaLink="false">https://medium.com/p/3501dffc5c2a</guid>
            <category><![CDATA[compose-multiplatform]]></category>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[engenharia]]></category>
            <category><![CDATA[kotlin-multiplatform]]></category>
            <category><![CDATA[android]]></category>
            <dc:creator><![CDATA[Pedro Cardoso]]></dc:creator>
            <pubDate>Tue, 30 Sep 2025 11:08:37 GMT</pubDate>
            <atom:updated>2025-09-30T11:08:34.336Z</atom:updated>
            <content:encoded><![CDATA[<p>Em 2024, iniciamos uma nova etapa no desenvolvimento mobile na <strong>Frete.com (Fretebras) </strong>🚚🦄<strong>,</strong> a adoção do <strong>Kotlin Multiplatform (KMP)</strong> para compartilhar a maior parte do nosso código entre Android e iOS.</p><p>E neste artigo, quero compartilhar a <strong>visão de bastidores </strong>(com a minha minha opinião) de como essa transição tem acontecido na prática: <strong>decisões técnicas, aprendizados, desafios</strong> e como conseguimos <strong>entregar mais com menos.</strong></p><p>Se você já pensou em usar KMP ou está apenas curioso para entender como uma migração desse nível funciona no dia a dia aqui vai um relato real, com bastidores técnicos e dicas que podem evitar armadilhas no seu projeto.</p><p>Ei… Alguns assuntos e discussões que embasaram decisões dariam artigos próprios (e alguns pretendo escrever em breve, contando mais detalhes sobre), mas neste texto vou focar nos pontos principais que guiaram essa transformação e dicas rápidas com exemplo sobre o código. <strong>Bora nessa?</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*f3F7BylB4qJ0YAZX1_IYNg.png" /></figure><h3>1. De onde partimos</h3><p>De forma super resumida, a <strong>Fretebras</strong> conecta caminhoneiros a transportadoras. Durante muito tempo, o público-alvo usava majoritariamente Android, e por isso, o app era <strong>inteiramente nativo e estava disponível apenas para o Android.</strong> Os usuários de dispositivos iOS tinham apenas a opção de uma versão Web com funcionalidades limitas.</p><p>Com o crescimento do uso de iOS nesse público, surgiu a necessidade e oportunidade de oferecer uma experiência mobile-first.</p><p>Diante disso, avaliamos as opções: manter versões nativas, usar tecnologias crossplataforma ou apostar no compartilhamento de código.</p><p>Entre as avaliações decidimos seguir com o KMP, mas, calma, vamos entrar nos detalhes dessa decisão.</p><h3>2. Por que migrar para KMP?</h3><ol><li>O app <strong>Fretebras era</strong> <strong>100% Kotlin</strong>, com ~90% das telas em <strong>Jetpack Compose</strong> (MVVM + uma boa cobertura de testes unitários).</li><li><strong>Aproveitar a expertise existente, </strong>tínhamos um chapter composto por pessoas com <strong>domínio do ecossistema Android e Kotlin</strong></li><li><strong>Reaproveitamento de código, muito código, </strong>ViewModels, UseCases, Repositories e UI em Compose.</li><li><strong>Entregar mais rápido</strong>, evitar retrabalho em features duplicadas para o iOS.</li></ol><h3>3. A transição em três fases</h3><h4><strong>Fundação</strong></h4><ul><li><strong>Criamos um time específico</strong> para estruturar e construir a base.</li><li>Nosso foco foi criar APIs que tornassem o <strong>desenvolvimento KMP praticamente indistinguível do Android </strong>reduzindo a carga cognitiva dos times de produto<strong>.</strong></li><li><strong>Escrevemos classes e objetos com APIs</strong> similares às que já usávamos no Android. (Mas sem um Context God)</li></ul><h4><strong>Integração</strong></h4><ul><li><strong>Lançamos um MVP</strong> por volta de setembro de 2024.</li><li><strong>Começamos a integrar o chapter</strong>: talks, trilhas, base de conhecimento, pair programming e o início da disseminação de tarefas.</li><li>Também realizamos <strong>overviews técnicos, escrita de documentações internas e mantivemos um diálogo contínuo</strong>.</li></ul><h4><strong>Avaliação</strong></h4><ul><li>Com o <strong>KMP em produção</strong>, passamos a focar na identificação de oportunidades de melhoria. <strong>Automatizamos a coleta de métricas</strong> de desenvolvimento.</li><li>Usamos ferramentas como o <strong>Konsist</strong> (Tipo um linter pra estrutura do projeto) para <strong>metrificar o esforço técnico da migração por feature/fluxo</strong>.</li><li><strong>Delegamos via chapter</strong> as adaptações para as squads, para tentar ser transparente com o produto. 👀</li></ul><h3>4. Princípios de código que fizeram diferença</h3><ol><li><strong>Clean Architecture</strong>, <strong>SOLID </strong>e <strong>MVVM</strong>: adotamos esses pilares, criando mecanismos que garantem sua adoção, simplificando assim a transição. Adotando esses princípios, grande parte do código pode ser reaproveitada, aplicando apenas pequenas adaptações.</li><li><strong>Desacoplamento de APIs de plataforma: </strong>nada de Context em <em>composables</em>. (Eu sei, de forma única isso não é um problema, mas conforme o projeto escala, numericamente, se torna uma coisa a mais pra resolver)</li><li><strong>Testes unitários em primeiro lugar:</strong> isso nos deu segurança para modularizar e portar ViewModels, UseCases e Repositories de forma quase instantânea.</li></ol><h3>5. Priorize Bibliotecas KMP‑friendly</h3><p>Priorize dependências que sejam <strong>Kotlin‑first</strong>, e de preferência já compatíveis com KMP. A tabela a seguir mostra as opções que adotamos e o que elas substituem no Android.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e09128914df9d1cc6bd6baebf88f15ac/href">https://medium.com/media/e09128914df9d1cc6bd6baebf88f15ac/href</a></iframe><p>🧈 <strong>Dica de ouro:</strong> sempre esconda dependências atrás de <em>interfaces</em>.</p><h3>6. <strong>Automatize mais e cobre menos</strong></h3><p>Use linters estruturais como o <strong>Konsist</strong> para garantir que as definições e práticas sejam seguidas, e assim delegar menos coisas para revisão manual. Automatize o que for possível e fizer sentido para garantir que o projeto está caminhando na direção esperada, adicione uma checagem e veja se o código novo está sendo testado, use a criatividade.</p><pre>import com.lemonappdev.konsist.api.assertions.*<br>import com.lemonappdev.konsist.api.ext.*<br>import org.junit.Test<br><br>class DomainNoAndroidImportsTest {<br>    @Test<br>    fun `domain não deve importar pacotes Android`() {<br>        Konsist<br>            .scopeFromProject()<br>            .files<br>            .filter { it.resideInPath(&quot;..domain..&quot;)<br>            .assertTrue { <br>                it.imports.none { it.hasNameContaining(&quot;android&quot;)<br>             }<br>    }<br>}</pre><blockquote><em>Se algum arquivo do módulo </em><strong><em>domain</em></strong><em> importar </em><em>android.* ou </em><em>androidx.*, o build falha.</em></blockquote><h3>7. Desafios</h3><p>Apesar dos benefícios, o uso do KMP também trouxe desafios. Por ser uma tecnologia ainda em evolução, <strong>a documentação oficial nem sempre refletia a realidade prática (também vale pra libs da comunidade)</strong>. Em vários momentos seguimos a documentação da JetBrains, mas na hora da implementação, exigiam ajustes ou os famosos <em>workarounds </em>😜.</p><h3>7.1 Tipos genéricos em Flows no iOS</h3><p>No iOS, por exemplo, ainda não temos uma geração estável de código Swift direto a partir do KMP. Isso nos obrigou a criar soluções internas, binds, mappers e coisas do tipo.</p><p>Um exemplo clássico e conhecido é a interoperabilidade com o Objective-C/Swift: quando se exporta um Flow&lt;T&gt;, o tipo T se perde no binding e vira Flow&lt;Any?&gt;.</p><p>Uma dica que facilitou bastante: use o <strong>SKIE</strong>, uma biblioteca que gera wrappers e binds para o Swift de forma eficiente e estável.</p><pre>// Declaração no código kotlin compartilhado (common main)<br>object FlowContainer {<br>  val myFlow: Flow&lt;MyCustomType&gt; = ...<br>}</pre><p>Sem <strong>SKIE</strong>:</p><pre>// Swift - erro de compilação<br>let mapped = FlowContainer.myFlow.map { value in<br>    // ❌ error! &#39;Flow&#39; has no member &#39;map&#39;<br>}<br><br>// myFlow é exposto como Kotlinx_coroutines_coreFlow</pre><p>Com <strong>SKIE</strong>, Flow vira AsyncSequence&lt;T&gt; e o uso fica nativo:</p><pre>// Swift (com SKIE) - myFlow vira AsyncSequence&lt;MyCustomType&gt;<br>Task {<br>    for try await item in FlowContainer.myFlow {<br>        // ✅ Uso nativo de AsyncSequence<br>        print(item)<br>    }<br>}<br><br>// É possível encadear operadores<br>let cancellable = FlowContainer.myFlow<br>    .map(\.property)<br>    .sink { result in<br>        print(result)<br>    }<br><br>// myFlow agora é AsyncSequence&lt;MyCustomType&gt;</pre><h3>7.2 Falta de suporte de libs oficiais</h3><p>Muitas libs super usadas no ecossistema android atualmente não possuem suporte oficial ao KMP, e isso pode te afastar do KMP, mas não deixe que isso te assuste, toda lib nativa ainda pode ser usada no seu projeto. Por exemplo, veja como é possível usar o remote config:</p><pre>// No código kotlin compartilhado, defina uma interface comum<br>interface RemoteConfigProvider {<br>    suspend fun getString(key: String): String<br>    suspend fun getBoolean(key: String): Boolean<br>}</pre><p>Implementações para <strong>Android (Firebase)</strong> e <strong>iOS</strong>:</p><pre>// no androidMain, estenda a interface e forneça os resultados necessários<br>class AndroidRemoteConfigProvider(<br>    private val remoteConfig: FirebaseRemoteConfig = FirebaseRemoteConfig.getInstance()<br>) : RemoteConfigProvider {<br><br>    override fun getString(key: String) = remoteConfig.getString(key)<br><br>    override fun getBoolean(key: String) = remoteConfig.getBoolean(key)<br>}</pre><p>Do lado do iOS você tem duas opções, estender a interface ainda em kotlin no iosMain, ou estenda-la no swift e fornecer essa implementação via injeção de dependências. Por exemplo, estendendo no swift e usando koin:</p><pre>import ComposeApp<br>import FirebaseRemoteConfig<br><br>// estenda sua interface <br>class IOSRemoteConfigProvider : RemoteConfigProvider {<br><br>  let remoteConfig = RemoteConfig.remoteConfig()<br><br>  func getString(key: String) -&gt; String {<br>    return remoteConfig.configValue(forKey: key).stringValue ?? &quot;&quot;<br>  }<br><br>  ...<br>}<br><br>// No swift appDelegate ou algum outro lugar pra iniciar o seu DI container<br>DIContainer.initModules(<br>  remoteConfigProvider: { IOSRemoteConfigProvider() } <br>)</pre><p>No iosMain, forneça uma forma acessível do swift passar as suas dependências nativas resolvidas via interface</p><pre>// iosMain<br>object DIContainer {<br><br>  fun initModules(<br>    remoteConfigProvider: () -&gt; RemoteConfigProvider<br>  ) {<br>    initKoin {<br>      module {<br>        single&lt;RemoteConfigProvider&gt; {<br>          remoteConfigProvider()<br>        }<br>      }<br><br>      // seus outros módulos<br>      // ...<br>    }<br>  }<br>}<br><br>// Agora você pode usar o remoteConfig em qualquer lugar<br>// do seu código kotlin compartilhado<br>class Sample : KoinComponent {<br>  private val remoteConfig by inject&lt;RemoteConfigProvider&gt;()<br>}<br></pre><h3>8. Lições aprendidas</h3><ul><li>Construa <strong>APIs amigáveis</strong> para quem já programa para o Android; o iOS vira bônus.</li><li><strong>Automatize o que puder</strong> <em>e fizer sentido, </em>use<em> </em>Konsist, linters e outros, alivie o <em>code‑review</em>.</li><li><strong>Testes automatizados são a rede de segurança</strong> e na migração vão dar mais confiança ao time.</li><li><strong>Esteja preparado para <em>workarounds</em></strong><em>, </em>mas deixe documentado e tente remover assim que possível.</li><li><strong>Respeite a arquitetura definida desde o início, </strong>manter as camadas bem separadas evita acoplamentos, reduz suas dívidas e torna a troca de implementações muito mais simples.</li><li><strong>O ecossistema do KMP avança,</strong> desafios que ontem exigiam <em>workarounds</em> recebem correções ou soluções quase diariamente e de forma constante, não tenha medo.</li></ul><h3>Conclusão</h3><p>A jornada aqui na Fretebras com Kotlin Multiplatform e a transição continua, mas até aqui os <strong>resultados são verdadeiramente incríveis</strong>! Conseguimos evitar retrabalho, aumentar a velocidade de entrega e manter uma base de código consistente entre Android e iOS.</p><p><strong>Claro, houveram desafios</strong>, documentação incompleta, bibliotecas sem suporte, workarounds necessários. Mas cada obstáculo trouxe aprendizados valiosos, que hoje nos permitem evoluir mais rápido e com mais confiança.</p><p><strong>Se você trabalha com mobile, se está aprendendo ou sua empresa está pensando em migrar para KMP</strong>, espero que esse relato ajude a <strong>clarear os caminhos, evitar armadilhas</strong> e de <strong>certa forma</strong>, <strong>inspirar e embasar boas decisões.</strong></p><p>Se quiser bater um papo ou trocar ideias, <strong>me chama no LinkedIn ou comenta aqui</strong> 👇</p><p>— — — — — — —<br><em>Pedro Henrique Cardoso — </em><a href="https://www.linkedin.com/in/pedro-hcardoso/"><em>LinkedIn/pedro-hcardoso</em></a><br>Senior Android Developer at <a href="https://medium.com/fretebras-tech">Fretebras</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3501dffc5c2a" width="1" height="1" alt=""><hr><p><a href="https://medium.com/fretecom-tech/a-jornada-da-fretebras-com-kotlin-multiplatform-kmp-desafios-e-aprendizados-3501dffc5c2a">A jornada da Fretebras com Kotlin Multiplatform (KMP): desafios e aprendizados</a> was originally published in <a href="https://medium.com/fretecom-tech">Frete.com Tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Pre-flight, CORS e como eles impactam usuários]]></title>
            <link>https://medium.com/fretecom-tech/pre-flight-cors-e-como-eles-impactam-usu%C3%A1rios-9251bbe7a927?source=rss----a43f9b9429a---4</link>
            <guid isPermaLink="false">https://medium.com/p/9251bbe7a927</guid>
            <category><![CDATA[preflight-request]]></category>
            <category><![CDATA[cors]]></category>
            <dc:creator><![CDATA[Kelvin Romero]]></dc:creator>
            <pubDate>Mon, 28 Apr 2025 21:26:00 GMT</pubDate>
            <atom:updated>2025-04-29T13:56:55.633Z</atom:updated>
            <content:encoded><![CDATA[<h4>Por que só eu to tomando erro de CORS?</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*V6Iqo6cdh-Hu7jJsgTZ5ag.png" /><figcaption>Imagem também gerada por IA</figcaption></figure><p>Esses dias, um desenvolvedor me procurou com uma dúvida curiosa: o problema de CORS na aplicação já havia sido resolvido, mas ele <strong>continuava</strong> tomando erro de CORS no navegador.<br> Fui investigar, e achei que valia compartilhar aqui o que descobri — até porque pode ser útil para outros também.</p><p>(Disclaimer: este texto foi escrito com a ajuda do ChatGPT, ajustado para o contexto da Fretebras.)</p><h3>O que é CORS?</h3><p>CORS (Cross-Origin Resource Sharing) é uma proteção que o <strong>navegador</strong> aplica para evitar que aplicações web façam requisições para domínios diferentes do seu sem autorização explícita.</p><p>Ou seja: mesmo que o servidor responda corretamente, o navegador pode <strong>bloquear a resposta</strong> se os cabeçalhos de CORS não estiverem certos.</p><h3>O que aconteceu?</h3><p>O servidor já estava configurado para permitir as requisições, mas o navegador do dev continuava barrando por CORS.<br> O motivo? <strong>Cache de preflight</strong>.</p><p>O navegador tinha guardado uma resposta antiga de uma requisição OPTIONS (a chamada preflight), que dizia que o acesso não era permitido. Mesmo depois da correção no servidor, o navegador ainda <strong>confiava na resposta antiga</strong>.</p><h3>Como funciona o OPTIONS?</h3><p>Antes de certas requisições (por exemplo, POSTs com cabeçalhos customizados), o navegador envia automaticamente uma requisição OPTIONS para perguntar ao servidor:</p><blockquote><em>“Eu posso fazer essa requisição?”</em></blockquote><p>Se o servidor responde positivamente, o navegador segue para a requisição real. Se não, ele bloqueia.</p><p>Essa resposta pode incluir o cabeçalho Access-Control-Max-Age, que diz por quanto tempo o navegador pode <strong>confiar</strong> nessa autorização sem perguntar de novo.</p><h3>Como funciona o cache?</h3><p>Se o servidor responde, por exemplo, com:</p><pre>Access-Control-Max-Age: 3600</pre><p>O navegador entende que pode usar essa autorização por 3600 segundos (1 hora). Durante esse período, ele <strong>não reenvia o preflight</strong>.</p><p>O problema é que, se o servidor mudar a configuração durante esse tempo, o navegador <strong>não sabe</strong> — ele continua confiando na resposta antiga até o cache expirar.</p><p>Isso explica porque o desenvolvedor ainda via o erro, mesmo depois da correção.</p><h3>Como corrigir</h3><p><strong>Abra as Ferramentas para Desenvolvedores</strong> pressionando F12<br>Com as Ferramentas para Desenvolvedores abertas, <strong>clique com o botão direito do mouse no botão &quot;Recarregar&quot;</strong> (o ícone de seta circular na barra de endereço).<br>No menu de contexto que aparece, selecione <strong>&quot;Limpar cache e recarregar forçosamente&quot;</strong></p><h3>Em resumo</h3><ul><li>O CORS é uma proteção do navegador, não do servidor.</li><li>O navegador pode <strong>cachear a autorização</strong> de CORS com base no preflight (OPTIONS).</li><li>Mudanças no servidor podem <strong>não ser percebidas imediatamente</strong> por quem tem cache armazenado, ou seja, o desenvolvedor não consegue invalidar o cache do lado do usuário</li><li>Soluções possíveis incluem forçar o refresh (limpar cache) ou usar Access-Control-Max-Age menores.</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9251bbe7a927" width="1" height="1" alt=""><hr><p><a href="https://medium.com/fretecom-tech/pre-flight-cors-e-como-eles-impactam-usu%C3%A1rios-9251bbe7a927">Pre-flight, CORS e como eles impactam usuários</a> was originally published in <a href="https://medium.com/fretecom-tech">Frete.com Tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Usando State, Event e Effects com Jetpack Compose: Um Guia Prático]]></title>
            <link>https://medium.com/fretecom-tech/usando-state-event-e-effects-com-jetpack-compose-um-guia-pr%C3%A1tico-1c54743f7461?source=rss----a43f9b9429a---4</link>
            <guid isPermaLink="false">https://medium.com/p/1c54743f7461</guid>
            <category><![CDATA[android]]></category>
            <category><![CDATA[state-management]]></category>
            <category><![CDATA[kotlin-multiplatform]]></category>
            <category><![CDATA[mobile-development]]></category>
            <category><![CDATA[jetpack-compose]]></category>
            <dc:creator><![CDATA[Jorge Carvalho]]></dc:creator>
            <pubDate>Wed, 02 Apr 2025 23:30:39 GMT</pubDate>
            <atom:updated>2025-04-02T23:30:39.104Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*btWAN65O9xMOSOf_cYwvHg.jpeg" /></figure><p>Se você está começando a desenvolver aplicativos Android com <strong>Jetpack Compose</strong>, provavelmente já ouviu falar sobre gerenciamento de estado. Mas como organizar sua lógica de forma clara, previsível e escalável? Uma abordagem poderosa é usar o padrão baseado em <strong>State</strong>, <strong>Event</strong> e <strong>Effects</strong>. Neste artigo, vou te mostrar como funciona esse padrão, por que ele é útil e como implementá-lo em um exemplo prático: um contador simples.</p><h3>O que são State, Event e Effects?</h3><p>Antes de mergulharmos no código, vamos entender os conceitos:</p><ul><li><strong>State</strong>: Representa o estado atual da sua interface de usuário (UI). É como uma “fotografia” do que está sendo exibido na tela em um dado momento.</li><li><strong>Event</strong>: São as ações ou intenções do usuário, como clicar em um botão ou digitar algo. Eles dizem à lógica do aplicativo o que o usuário quer fazer.</li><li><strong>Effects</strong>: Efeitos colaterais que não pertencem ao estado principal, como exibir uma mensagem temporária (Snackbar), Toast ou navegar para outra tela, por exemplo.</li></ul><p>Esse trio forma um fluxo unidirecional: os eventos fluem “para cima” (da UI para a lógica), o estado flui “para baixo” (da lógica para a UI), e os efeitos são tratados separadamente. Esse padrão é inspirado em arquiteturas como <strong>MVI (Model-View-Intent)</strong> e <strong>Unidirectional Data Flow (UDF)</strong>, mas vamos mantê-lo simples e prático.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nKu2tnuRcRzM2N-MwlRGaQ.png" /></figure><h3>Por que usar esse padrão?</h3><ul><li><strong>Clareza</strong>: O fluxo de dados é previsível e fácil de entender.</li><li><strong>Testabilidade</strong>: A lógica fica isolada, facilitando testes unitários.</li><li><strong>Escalabilidade</strong>: Adicionar novas funcionalidades é mais organizado.</li></ul><p>Agora, vamos colocar a mão na massa com um exemplo.</p><h3>Exemplo: Um Contador com Jetpack Compose</h3><p>Vamos criar um aplicativo de contador com dois botões: “Incrementar” e “Decrementar”. Sempre que o usuário clicar em um botão, o contador será atualizado e uma mensagem temporária será exibida.</p><h3>Passo 1: Definindo State, Event e Effects</h3><p>Primeiro, criamos as estruturas básicas em Kotlin:</p><ul><li><strong>CounterState</strong>: Armazena o valor atual do contador.</li><li><strong>CounterEvent</strong>: Define as ações possíveis (incrementar ou decrementar).</li><li><strong>CounterEffect</strong>: Representa o efeito colateral de mostrar uma mensagem.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5DpBbEihSJBATiP5RDVcNA.png" /></figure><h3>Passo 2: Criando a ViewModel</h3><p>A ViewModel é onde a mágica acontece. Ela processa os eventos, atualiza o estado e emite efeitos. Usaremos StateFlow para o estado e SharedFlow para os efeitos.</p><h4>Por que usar StateFlow para o estado?</h4><p>StateFlow é ideal para armazenar e expor o estado porque:</p><ul><li><strong>Mantém o último valor</strong>: Sempre guarda o estado mais recente, garantindo que qualquer nova coleta tenha acesso ao estado atualizado imediatamente.</li><li><strong>É observável e reativo</strong>: Quando o valor muda, todos os coletores ativos são notificados automaticamente.</li><li><strong>Evita repetições desnecessárias</strong>: Se o mesmo estado for emitido novamente, ele não notifica os coletores, otimizando o fluxo de dados.</li></ul><p>Isso faz com que StateFlow seja perfeito para representar a UI, pois a UI precisa apenas do estado mais atual e deve ser atualizada automaticamente quando ele mudar.</p><h4>Por que usar SharedFlow para os efeitos?</h4><p>SharedFlow é mais adequado para efeitos porque:</p><ul><li><strong>Não mantém estado</strong>: Diferente do StateFlow, ele não guarda o último valor emitido. Isso é essencial para efeitos transitórios como exibir um Snackbar ou navegar para outra tela.</li><li><strong>Pode reemitir eventos múltiplas vezes</strong>: Como efeitos precisam ser acionados sempre que ocorrem (e não apenas quando há uma mudança de estado), SharedFlow permite essa emissão contínua.</li><li><strong>Suporta diferentes estratégias de buffer e replay</strong>: Podemos configurar o SharedFlow para reemitir eventos recentes se necessário.</li></ul><p>Se usássemos StateFlow para os efeitos, o Snackbar poderia ser reexibido desnecessariamente ao recompor a UI, o que não é o comportamento desejado.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gaY855qC88Nb5kz0xBCSkQ.png" /></figure><p>Aqui, a ViewModel:</p><ul><li>Mantém o estado em _state e o expõe como um StateFlow.</li><li>Emite efeitos em _effect usando um SharedFlow.</li><li>Atualiza o estado e emite efeitos com base nos eventos recebidos.</li></ul><h3>Passo 3: Construindo a UI com Compose</h3><p>Agora, criamos o Composable que consome o estado, envia eventos e lida com efeitos:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*O9C20y8d1k33NLQs0yZ40w.png" /></figure><ul><li><strong>collectAsStateWithLifecycle()</strong>: Observa o estado e atualiza a UI automaticamente.</li><li><strong>LaunchedEffect</strong>: Escuta os efeitos e exibe uma Snackbar quando necessário.</li><li><strong>Eventos</strong>: Enviados para a ViewModel via onEvent.</li></ul><h3>Como Tudo Funciona Junto?</h3><ol><li>O usuário clica em “Incrementar”.</li><li>Um evento CounterEvent.Increment é enviado para a ViewModel.</li><li>A ViewModel atualiza o estado (count + 1) e emite um efeito (ShowMessage).</li><li>O Composable reflete o novo estado (mostra o contador atualizado) e exibe a Snackbar com a mensagem.</li></ol><p>Esse fluxo unidirecional mantém a lógica limpa e previsível.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/480/1*wqytHSItTm36JG3VEaCmEg.gif" /></figure><h3>Benefícios e Quando Usar</h3><p>Esse padrão é ótimo para:</p><ul><li>Aplicativos com muitas interações de usuário.</li><li>Projetos que precisam de testes robustos.</li><li>Equipes que querem um código mais organizado.</li></ul><p>Se o seu aplicativo for muito simples (como uma tela estática), talvez não valha a pena tanta estrutura. Mas, para algo mais complexo, como um formulário ou uma lista dinâmica, ele brilha.</p><h3>Conclusão</h3><p>Usar <strong>State</strong>, <strong>Event</strong> e <strong>Effects</strong> com Jetpack Compose é uma maneira elegante de gerenciar sua UI. Ele combina bem com as ferramentas modernas do Android, como StateFlow e SharedFlow, e segue os princípios recomendados pelo Google. Experimente no seu próximo projeto e veja como ele pode simplificar sua vida como desenvolvedor!</p><p>Se quiser aprofundar, confira a <a href="https://developer.android.com/jetpack/compose">documentação oficial do Jetpack Compose</a> ou explore bibliotecas como <a href="https://medium.com/@engineer.marwatalaat/orbit-mvi-and-containerhost-acf78b64d2ac">Orbit MVI</a>. Happy coding!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1c54743f7461" width="1" height="1" alt=""><hr><p><a href="https://medium.com/fretecom-tech/usando-state-event-e-effects-com-jetpack-compose-um-guia-pr%C3%A1tico-1c54743f7461">Usando State, Event e Effects com Jetpack Compose: Um Guia Prático</a> was originally published in <a href="https://medium.com/fretecom-tech">Frete.com Tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Feature Store (Parte 1): Acelerando o Desenvolvimento de Modelos de Machine Learning]]></title>
            <link>https://medium.com/fretecom-tech/feature-store-parte-1-acelerando-o-desenvolvimento-de-modelos-de-machine-learning-0ef7a2ed702a?source=rss----a43f9b9429a---4</link>
            <guid isPermaLink="false">https://medium.com/p/0ef7a2ed702a</guid>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[feature-store]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[mlops]]></category>
            <dc:creator><![CDATA[Rhian Lopes da Costa]]></dc:creator>
            <pubDate>Mon, 02 Dec 2024 12:49:07 GMT</pubDate>
            <atom:updated>2025-12-08T23:46:40.136Z</atom:updated>
            <content:encoded><![CDATA[<h2>Feature Store: Acelerando o Desenvolvimento de Modelos de Machine Learning</h2><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Ou63-IeF72GuewCE9BOT4A.png" /></figure><h3>Introdução</h3><p>Nessa sequencia de artigos, vamos entender o conceito de Feature Store, o cenário onde se aplica e o uso na prática com o Feast, uma plataforma open-source de feature store projetada para gerenciar e armazenar as features, que utilizaremos no treinamento e inferência de um modelo de Machine Learning.</p><h3>O que são features?</h3><p>Features, ou características em português, são as variáveis ou atributos usados como entrada para os modelos. Elas representam os aspectos relevantes dos dados que o modelo utiliza para fazer previsões ou classificações. A qualidade e relevância das features são cruciais para o desempenho de qualquer modelo.</p><h4>Exemplos de Features:</h4><p>Features para um modelo de previsão de preços de imóveis:</p><ul><li>Área do imóvel</li><li>Número de quartos</li><li>Localização</li><li>Ano de construção</li></ul><blockquote>As Features podem ser numéricas, representando valores contínuos ou discretos, como idade e salário. As categóricas são qualitativas e não ordenadas, como cor dos olhos ou país, enquanto as binárias possuem apenas dois valores, como sim/não. Features temporais capturam dados relacionados ao tempo, como datas e horários. Já as textuais são convertidas em números para uso nos modelos, como vetores ou embeddings. Cada tipo contribui de forma distinta para o desempenho do modelo.</blockquote><h3>O que é Feature Store?</h3><p>Feature Store é um sistema de armazenamento e gerenciamento de dados, a fim de centralizarmos e organizarmos features, características de entidades, que são utilizadas por modelos de machine learning.</p><p>Imagine uma linha do tempo de uma entidade fictícia, onde ao longo do tempo foram coletadas algumas features, como no exemplo abaixo:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nFHEvG4a12b7uOQmT7rYBg.jpeg" /></figure><p>Onde podemos representar essas features em forma de tabela:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*tllobabGmi97ZtN7LmXABA.jpeg" /></figure><p>Nessa tabela podemos observar cada uma das nossas features ao longo do tempo. Podemos organizar as features a fim de configurarmos para uso via Feature Store, onde encontramos três tipos de colunas:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*glUBAey8rjXkpIFoXVqyJg.jpeg" /></figure><p>Sendo elas a partição por data e hora, identificador da entidade e as features em si, onde a partição se trata do campo de timestamp que registra o momento do estado da entidade, em seguida o identificador da entidade e por fim as features da entidade.</p><h3>Que problema ela resolve?</h3><p>As Features ficam armazenadas de forma distribuida, entre banco de dados de diferentes microserviços e fluxo de dados de streaming, elas precisam ser combinadas e conciliadas para gerar previsões precisas e serem entregues em baixa latência, viabilizando seu uso em iniciativas como recomendação ou detecção de fraude em tempo real, por exemplo.</p><p>Com as features centralizadas em uma Feature Store, isso nos permite que as equipes colaborem e compartilhem features entre as iniciativas. Reduzindo o retrabalho e acelerando o desenvolvimento de projetos de machine learning, além de possibilitar o versionamento, governança e controle de qualidade das features.</p><p>A Feature Store pode ser utilizada tanto em treinamento de modelos de ML por meio de features Offline, quanto em inferências em modelos de ML com features Online, ou seja, features Real-time em baixa latência.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IgBA1Dwn73zbJl52eND2vA.jpeg" /></figure><blockquote>O uso da Feature Store para treinamento de modelos de ML, buscamos as features da entidade a partir da data que desejarmos, onde obtemos como retorno o estado atual das features nessa data. Por exemplo, a entidade com ID 1234 em 2009 teria a Profissão &quot;Estudante do Fundamental&quot; e residente da cidade de &quot;Esteio&quot;, obtendo as features de maneira histórica.</blockquote><blockquote>E o uso da Feature Store para inferência, buscamos as features da entidade a partir da data atual, onde obtemos como retorno o estado atual das features. Por exemplo, a entidade com ID 1234 atualmente teria a Profissão &quot;Engenheiro de ML&quot; e residente da cidade de &quot;Porto Alegre&quot;, obtendo as features em baixa latência.</blockquote><h3>Cenário de Uso</h3><p>Imagine uma empresa de e-commerce com diversos microserviços, cada um gerenciando uma parte específica do processo de vendas: gerenciamento de estoque, informações de produtos, perfil de usuário, histórico de compras, entre outros. Cada microserviço possui seu próprio banco de dados, onde armazena dados específicos relevantes para suas operações.</p><p>Além disso, o sistema de e-commerce processa dados de comportamento de usuário em tempo real, como visualizações de produtos e cliques, transmitidos em um fluxo contínuo (streaming) a partir de eventos gerados pelos usuários no site.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Lb-zz_vGm85eQE1ZYV_Kkg.jpeg" /></figure><p>Essas features, distribuídas entre bancos de dados de diferentes microserviços e o fluxo de dados de streaming, precisam ser combinadas e conciliadas para gerar previsões precisas e entregues em baixa latência, onde podemos utilizar em iniciativas de recomendações de produtos ou detecção de fraudes em tempo real, por exemplo.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-gmyvFqrRiPmU-Hpc60QQA.jpeg" /></figure><p>Caso as aplicações não estejam preparadas para retornar as informações necessárias, cenário comum de ocorrer, isso vai demandar semanas ou meses de trabalho entre times. Ao fim das integrações, estaremos sujeito ao tempo de latência para obter essas informações, onde dependendo do cenário como recomendação ou fraude o tempo completo de resposta idealmente deve ser na casa de milissegundos ou máximo poucos segundos.</p><p>Além disso, acabamos criando uma dependência gigante entre as aplicações e times, caso seja necessário levar em consideração uma feature extra para a realização de um teste AB, isso resultar em semanas de implementação da feature nova. Onde ao fim da implementação pode ser que ela não seja tão performática quanto o esperado 😅.</p><p>Por fim, acabamos entrando nesse ciclo de dependência para evoluirmos a performance do Modelo de ML, podendo impactar diretamente o potencial obtido com a inteligência e onerando o tempo de reação da inteligência a novos comportamentos e fluxos do negócio.</p><h3>Feature Store</h3><p>A Feature Store tem como objetivo centralizar e conciliar essas informações históricas e em tempo real.</p><h4>Dados Históricos (Offline Store)</h4><p>Os dados das fontes de streaming ou em batch são ingeridos e periodicamente extraídos, transformados e disponibilizadas na Offline Store da Feature Store. Uma vez que esses dados estão processados, estão disponíveis para consumo histórico para treinamento de modelos de ML.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*h920ZTnqKb4aoM-rXJYUJg.jpeg" /></figure><h4><strong>Dados Real-time (Online Store)</strong></h4><p>As features processadas na Offline Store podem ser disponibilizadas em baixa latência na Online Store, sendo que as features disponibilizadas serão sempre o último estado da entidade.</p><p>As features podem ser disponibilizadas via materialização ou diretamente integradas a esteira de ingestão de dados de streaming, disponibilizando o estado mais recente das features em tempo real.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kEBg43JpcX9jtcRPtajbtA.jpeg" /></figure><h4>Materialização</h4><p>A materialização ocorre quando queremos atualizar as features da Online Store a partir das features da Offline Store, ou seja, atualizar as features que estão sendo entregues em baixa latência para modelos ML baseados na participação de tempo ou novas entidades, atualizando o estado mais recente das features da entidade.</p><p>A materialização ocorre periodicamente conforme a necessidade do negócio, é comum que features cadastrais como endereços, documentos e dados pessoais dificilmente são alteradas com frequência, nesse cenário a materialização poderia acontecer de forma diária, a fim de atualizar as features do estado da entidade.</p><p>Com isso, é comum o uso de ETL ou Cron para realizar o gatilho da materialização das features, sendo o estado mais recente da entidade para a Online Store.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*h38uXrXj9dY7YUnqD50jRA.jpeg" /></figure><h3>Cenário com Feature Store</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4fPVJABENxaUCYRVaSR9Tg.jpeg" /></figure><p>Com isso, chegamos no diagrama acima, sendo um diagrama completo contemplando as aplicações, banco de dados, ingestão e disponibilização desses dados via Datalake.</p><p>Onde a Feature Store, atende tanto o time de Ciência de Dados com features processadas para treinamento de modelos a partir da Offline Store, nesse caso, o nosso Datalake. Quanto as features utilizadas para a inferência de modelos, sendo entregues em baixa latência a partir da Online Store.</p><p>Com esse diagrama, todas as integrações que antes seriam necessárias para realizar a integração com o modelo de ML, a fim de obter todas as features para realizar a inferência, deixam de ser necessárias.</p><p>Para realizar a integração com o modelo de ML vão ser necessárias apenas as features de gatilho, como identificadores das entidades. Todas as demais features necessárias para a inferência serão obtidas pelo modelo via Feature Store em baixa latência a partir dos identificadores das entidades, por fim realizando a inferência junto ao modelo.</p><h3>Conclusão</h3><p>A Feature Store tem como objetivo base fornecer features para treinamento de modelos de ML e inferência. Ela não é uma bala de prata, a Feature Store necessita de uma esteira de ingestão e arquitetura de dados madura, isso inclui monitoramento para garantir sua estabilidade e escalabilidade.</p><p>Além disso, talvez a necessidade mais importante é um processo maduro de contrato de dados entre times de dados e tecnologia. Pois, o papel que anteriormente era exercido pelos times de tecnologia de buscar, integrar e conciliar os dados, agora é exercido pelo time dados, onde qualquer tipo de mudança no contrato de dados sem planejamento pode comprometer a esteira de ingestão e disponibilização desses dados.</p><p>Caso ocorra a quebra de contrato, compromete primeiramente a ingestão, no caso de dados Offline, como features cadastrais que já ocorre uma materialização de forma periódica para a Online Store, o impacto ainda pode ser minimizado. Mas no cenário de ingestão de streaming, um processo de recomendação pode ter sua performance diretamente impactada caso a localização obtida pela entidade seja com delay de 1 hora atrás, por conta de uma atualização de contrato de dados não planejada.</p><p>Com isso, a responsabilidade de manter a ingestão e os contratos de dados é compartilhada entre times de dados e tecnologia. Mas a responsabilidade de manter o monitoramento e escalabilidade da Feature Store é do time de MLOps, por meio de processos para inclusão de features e monitoramento para manter a qualidade das features armazenadas.</p><p>Dessa forma, a Feature Store tem potencial para se tornar uma peça chave da plataforma de MLOps, permitindo acelerarmos o processo de evolução de modelos de ML e desenvolvimento de novos projetos. Além de se adaptar rapidamente com novos fluxos e comportamentos da jornada do usuário na plataforma.</p><p>Conquistando independência com relação as features, permitindo uma evolução continua do modelo e sua performance, sem necessariamente novas integrações com os times de tecnologia, acelerando o desenvolvimento de modelos de ML.</p><p>Na parte 2 dessa sequência de artigos, vamos utilizar na prática o Feast como Feature Store, para treinarmos e realizarmos a inferência de um modelo de Machine Learning.</p><p>Obrigado pela sua atenção! E até breve 😄</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=0ef7a2ed702a" width="1" height="1" alt=""><hr><p><a href="https://medium.com/fretecom-tech/feature-store-parte-1-acelerando-o-desenvolvimento-de-modelos-de-machine-learning-0ef7a2ed702a">Feature Store (Parte 1): Acelerando o Desenvolvimento de Modelos de Machine Learning</a> was originally published in <a href="https://medium.com/fretecom-tech">Frete.com Tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Manipulação de Imagens no Kotlin Multiplatform com BitmapWrapper: Minha Experiência na…]]></title>
            <link>https://medium.com/fretecom-tech/manipula%C3%A7%C3%A3o-de-imagens-no-kotlin-multiplatform-com-bitmapwrapper-minha-experi%C3%AAncia-na-3bbed060d6ec?source=rss----a43f9b9429a---4</link>
            <guid isPermaLink="false">https://medium.com/p/3bbed060d6ec</guid>
            <category><![CDATA[kmp]]></category>
            <category><![CDATA[engenharia]]></category>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[kotlin-multiplatform]]></category>
            <dc:creator><![CDATA[EgitoFilho]]></dc:creator>
            <pubDate>Tue, 15 Oct 2024 17:37:17 GMT</pubDate>
            <atom:updated>2024-10-15T17:37:17.795Z</atom:updated>
            <content:encoded><![CDATA[<h3>Manipulação de Imagens no Kotlin Multiplatform com BitmapWrapper: Minha Experiência na Implementação</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BOu-NHLmhDQH7MKlymYQOA.png" /></figure><h3>Introdução</h3><p>Ao trabalhar em um projeto Kotlin Multiplatform, me deparei com um desafio interessante: como manipular imagens de forma eficiente em diferentes plataformas, mantendo o código compartilhado limpo e sustentável? Este foi um problema real que encontrei durante o desenvolvimento, e precisei implementar uma solução prática e multiplataforma para lidar com imagens — algo comum em muitos aplicativos modernos.</p><p>A necessidade surgiu quando tive que lidar com imagens carregadas de strings Base64, convertê-las para byte arrays para envio via API e, por fim, renderizá-las na UI tanto em Android quanto em iOS. Cada plataforma tem suas particularidades, e eu queria evitar duplicação de código e garantir consistência no comportamento da aplicação.</p><p>Foi nesse cenário que desenvolvi a solução BitmapWrapper, utilizando o poder do <strong>expect/actual</strong> do Kotlin para criar uma camada de abstração, facilitando o trabalho com imagens independentemente da plataforma. Neste artigo, vou compartilhar como cheguei à implementação dessa solução, os desafios que encontrei no caminho, e como você pode aplicá-la nos seus projetos multiplataforma.</p><h3>Solução com BitmapWrapper</h3><p>Quando me deparei com o desafio de manipular imagens multiplataforma, sabia que precisaria encontrar uma forma de unificar a lógica entre Android e iOS, sem comprometer a flexibilidade que cada plataforma exige. Foi nesse contexto que surgiu a ideia de criar a classe BitmapWrapper.</p><p>Minha primeira necessidade era manipular imagens de maneira eficiente: carregar de Base64, converter para bytes e renderizar na UI. A ideia era encapsular as diferenças entre Bitmap no Android e UIImage no iOS, mantendo o código compartilhado limpo. Então, parti para criar uma <strong>expect class</strong>, que seria implementada de forma específica para cada plataforma.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4mnWMggcOlhZ_Yh-4MdTfQ.png" /></figure><h4>O Papel da BitmapWrapper</h4><p>A classe BitmapWrapper serve como uma abstração para a manipulação de imagens. Em vez de lidar diretamente com Bitmap no Android ou UIImage no iOS, encapsulamos esses objetos nativos dentro da BitmapWrapper. Isso nos permite trabalhar com um único tipo no código compartilhado, sem precisar conhecer os detalhes de implementação de cada plataforma.</p><h4>Conversão Base64 para Imagem: fromBase64</h4><p>A função fromBase64 transforma uma string Base64 em uma instância de BitmapWrapper, permitindo carregar uma imagem de forma consistente. Cada plataforma tem sua própria maneira de decodificar strings Base64 e convertê-las em objetos de imagem.</p><h4>Conversão de Imagem para ByteArray: BitmapWrapper.toByteArray</h4><p>A função toByteArray converte a imagem encapsulada em um array de bytes. Isso é útil quando precisamos serializar a imagem para enviar em uma API ou armazená-la no banco de dados local. O formato de bytes pode variar (PNG, JPEG, etc.), dependendo das necessidades do projeto.</p><h3>Implementação Android</h3><p>No Android, utilizamos a classe Bitmap, que representa uma imagem em memória. A conversão entre formatos é facilitada pela API de compressão e decodificação do BitmapFactory.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qk8D4Y3Yp-45IJEV_auO9w.png" /></figure><p><strong>Detalhes Técnicos</strong>:</p><ul><li><strong>Base64</strong>: A classe Base64 do Android facilita a decodificação da string.</li><li><strong>BitmapFactory</strong>: Usada para converter o array de bytes decodificado em uma imagem.</li><li><strong>ByteArrayOutputStream</strong>: Utilizado para comprimir o Bitmap em um formato como PNG ou JPEG e obter o array de bytes.</li></ul><h3>Implementação iOS</h3><p>Com base nos aprendizados da comparação entre as plataformas, implementei a classe BitmapWrapper no código compartilhado e criei as funções específicas para cada plataforma. Para o Android, o processo foi relativamente simples, utilizando Bitmap e suas funções de conversão. No iOS, no entanto, tive que lidar com alguns detalhes mais específicos, especialmente na conversão de uma UIImage para um array de bytes.</p><h4>Conversão de Imagem para ByteArray no iOS</h4><p>Uma das partes mais desafiadoras foi a implementação da função toByteArray no iOS. Diferente do Android, que tem o método compress() para converter o Bitmap diretamente, no iOS precisei usar a função UIImageJPEGRepresentation() para obter os dados da imagem no formato JPEG. Aqui está o código que implementei:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*--0-uqZP8GmQWL1sTn1fxw.png" /></figure><p><strong>Detalhes Técnicos</strong>:</p><ul><li><strong>NSData</strong>: Utilizado para manipular dados binários no iOS, como a conversão de Base64 para uma imagem.</li><li><strong>UIImagePNGRepresentation</strong>: Função que converte um UIImage em dados PNG.</li><li><strong>Platform-Specific Logic</strong>: No iOS, trabalhamos diretamente com NSData para manipular binários e UIImage para a exibição de imagens.</li></ul><h3>Considerações Multiplataforma</h3><p>A chave para entender essa solução é a maneira como o Kotlin Multiplatform isola o código específico de cada plataforma, mantendo a interface pública limpa e consistente. No código compartilhado, não precisamos saber como cada plataforma lida com imagens; apenas interagimos com a API do BitmapWrapper, e o compilador cuida de ligar as implementações corretas para cada plataforma.</p><h3>Vantagens e Desafios</h3><h4>Vantagens</h4><ul><li><strong>Unificação da Lógica</strong>: O BitmapWrapper abstrai a complexidade, permitindo que desenvolvedores de diferentes plataformas trabalhem com imagens sem se preocupar com as particularidades de cada sistema operacional.</li><li><strong>Flexibilidade</strong>: Como cada plataforma pode ter necessidades específicas, a implementação actual permite ajustes finos, como o formato de compressão de imagem ou decodificação de Base64.</li><li><strong>Redução de Código Duplicado</strong>: Compartilhar a lógica comum em KMP evita a duplicação de código e facilita a manutenção.</li></ul><h4>Desafios</h4><ul><li><strong>Diferenças de API entre as Plataformas</strong>: Android e iOS têm APIs bastante diferentes para manipulação de imagens, o que pode exigir mais ajustes na implementação específica.</li><li><strong>Suporte de Recursos</strong>: Algumas plataformas podem ter funcionalidades extras ou limitações que precisam ser consideradas no desenvolvimento de soluções multiplataforma.</li></ul><h3>Conclusão</h3><p>O Kotlin Multiplatform oferece uma maneira eficiente de compartilhar lógica entre Android e iOS, especialmente para funcionalidades complexas como a manipulação de imagens. A solução apresentada, utilizando BitmapWrapper com expect/actual, permite a manipulação de imagens de maneira consistente, seja carregando-as de Base64, convertendo-as para bytes ou renderizando-as na UI. Com essa abordagem, conseguimos aproveitar o melhor de cada plataforma, mantendo o código compartilhado limpo e extensível.</p><h3>Considerações Finais</h3><p>Essa foi a solução que apliquei para resolver o problema de manipulação de imagens no meu projeto. No entanto, sei que existem outras abordagens possíveis, e estou totalmente aberto a discussões sobre alternativas ou melhorias para esse processo. Se você tem alguma sugestão ou outra experiência para compartilhar, adoraria ouvir e trocar ideias!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3bbed060d6ec" width="1" height="1" alt=""><hr><p><a href="https://medium.com/fretecom-tech/manipula%C3%A7%C3%A3o-de-imagens-no-kotlin-multiplatform-com-bitmapwrapper-minha-experi%C3%AAncia-na-3bbed060d6ec">Manipulação de Imagens no Kotlin Multiplatform com BitmapWrapper: Minha Experiência na…</a> was originally published in <a href="https://medium.com/fretecom-tech">Frete.com Tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Dica de Otimização em Go: A Ordem dos Campos em Structs Importa!]]></title>
            <link>https://medium.com/fretecom-tech/dica-de-otimiza%C3%A7%C3%A3o-em-go-a-ordem-dos-campos-em-structs-importa-de9f822b6119?source=rss----a43f9b9429a---4</link>
            <guid isPermaLink="false">https://medium.com/p/de9f822b6119</guid>
            <category><![CDATA[programming-languages]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[golang]]></category>
            <category><![CDATA[go]]></category>
            <category><![CDATA[development]]></category>
            <dc:creator><![CDATA[Brayann Barbosa]]></dc:creator>
            <pubDate>Fri, 04 Oct 2024 13:44:04 GMT</pubDate>
            <atom:updated>2024-10-04T13:44:04.774Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*f1CNnrTjnXcOvDvxp0efUA.png" /></figure><p>Na linguagem Go, a forma como organizamos campos em structs pode impactar significativamente o uso de memória e o desempenho do seu programa. Isso se deve aos conceitos de alinhamento de memória e padding, que garantem que os processadores acessem dados de forma eficiente.</p><h4>O Que é Alinhamento de Memória?</h4><p>Alinhamento de memória refere-se à disposição dos dados na memória física, de modo que possam ser acessados rapidamente. Cada tipo de dado tem um requisito de alinhamento baseado em seu tamanho. Por exemplo, um int64 deve começar em um endereço que seja um múltiplo de 8 bytes. Este alinhamento é crítico para que o processador manipule os dados com o mínimo de operações, aumentando a eficiência do acesso à memória.</p><h4>O Que é Padding?</h4><p>Padding é o espaço adicional, ou bytes, que o compilador insere entre campos de uma struct para garantir que os campos estejam corretamente alinhados. Esses bytes não armazenam informações úteis, mas são necessários para atingir o alinhamento correto.</p><p>Considere a struct abaixo:</p><pre>type StructA struct {<br>    A int32<br>    B int64<br>    C int32<br>}</pre><p>Neste caso, o compilador insere 4 bytes de padding entre A e B para que B comece em um endereço alinhado de 8 bytes, atendendo seu requisito de alinhamento. Sem o uso apropriado de padding, o processador poderá precisar mais de um ciclo de memória para operar nos dados, prejudicando a eficiência.</p><h4>Como Otimizar o Uso de Memória?</h4><p>A melhor prática é organizar os campos da struct do tipo maior para o menor, agrupando tipos semelhantes. Isso ajuda a minimizar o padding necessário, resultando em uma struct mais compacta e eficiente.</p><p>Exemplo de Struct Ineficiente:</p><pre>type StructA struct {<br>    A int32<br>    B int64<br>    C int32<br>}</pre><p>Exemplo de Struct Otimizada:</p><pre>type StructB struct {<br>    B int64 // maior tipo primeiro (8 bytes)<br>    A int32 // seguido pelos menores (4 bytes)<br>    C int32 // agrupado com A<br>}</pre><p>Ao reorganizar os campos como visto em StructB, evitamos o preenchimento desnecessário, resultando em uma estrutura mais eficiente em termos de memória.</p><h4>Ferramentas para Auxiliar na Otimização</h4><p>Para analisar e otimizar automaticamente a disposição dos campos em structs, você pode usar ferramentas como <a href="https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/fieldalignment">fieldalignment</a>. Essa é uma ferramenta nativa da plataforma do Go. Para instalar a última versão é só usar o comando:</p><pre>go install golang.org/x/tools/go/analysis/passes/fieldalignment/cmd/fieldalignment@latest</pre><p>Essa ferramenta tem duas funções principais, a primeira é apenas te dar um feedback do que pode ser alterado passando o comando “fieldalignment ./…” dentro da sua aplicação. E a segunda (e a minha favorita) é o ajuste “automágico” das structs com a flag “-fix”.<br>Um exemplo simples:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/998/1*TG0aZixqzcfKNNGOrDLawg.png" /></figure><p>Exemplo de Saída do fieldalignment:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/622/1*LOro-YOutzSlPY0oICnmxA.png" /></figure><p>Esta saída mostra as alterações na struct adequando para o melhor uso de memória.</p><h4>Conclusão</h4><p>Alinhar as structs de forma estratégica é mais do que um mero ajuste de estilo: é um passo essencial para garantir que seu código Go seja mais eficiente e performático. Em sistemas complexos, qualquer economia de memória ou melhora na performance pode ter um impacto significativo. Portanto, reserve um tempo para revisar e otimizar suas structs utilizando ferramentas automatizadas, assegurando o máximo desempenho para sua aplicação. 🚀</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=de9f822b6119" width="1" height="1" alt=""><hr><p><a href="https://medium.com/fretecom-tech/dica-de-otimiza%C3%A7%C3%A3o-em-go-a-ordem-dos-campos-em-structs-importa-de9f822b6119">Dica de Otimização em Go: A Ordem dos Campos em Structs Importa!</a> was originally published in <a href="https://medium.com/fretecom-tech">Frete.com Tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Tracing no Golang com o OpenTelemetry]]></title>
            <link>https://medium.com/fretecom-tech/tracing-no-golang-com-o-opentelemetry-4376b23c3484?source=rss----a43f9b9429a---4</link>
            <guid isPermaLink="false">https://medium.com/p/4376b23c3484</guid>
            <category><![CDATA[telemetria]]></category>
            <category><![CDATA[engenharia]]></category>
            <category><![CDATA[opentelemetry]]></category>
            <category><![CDATA[go]]></category>
            <category><![CDATA[tracing]]></category>
            <dc:creator><![CDATA[Vitor Hugo R Merencio]]></dc:creator>
            <pubDate>Thu, 12 Sep 2024 13:19:10 GMT</pubDate>
            <atom:updated>2024-09-12T13:19:10.479Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1kbQ4Sbwq2Ux_QZ2MjI2Zw.png" /></figure><p>O Golang é provavelmente uma das linguagens de programação que mais cresce em adoção em grandes empresas devido ao seu grande suporte de comunidade e ferramental que torna o dia a dia com a linguagem fácil de manter e escalar.</p><p>Neste cenário onde grandes empresas estão incluindo o Golang em suas linguagens preferidas para novos times e projetos, cresce a necessidade de adoção a capacidade de observabilidade aos grandes pacotes da comunidade para que permita acompanhar de forma mais detalhada como nossa aplicação interage com esses pacotes.</p><h3>Como usar o OpenTelemetry no Golang</h3><p>Para esse exemplo vou utilizar o <a href="https://gin-gonic.com/">Gin</a> que é um famoso pacote que nos ajuda na construção de API’s flexíveis e com ótimo suporte de comunidade, e vamos precisar dos seguintes itens instalados em nossa máquina para funcionar corretamente:</p><ul><li>Docker</li><li>Go 1.23</li></ul><p>Dito isso vamos iniciar o nosso projeto com o go mod init e nomear nosso projeto da forma que fizer sentindo, e após isto, instalar os seguintes pacotes abaixo:</p><pre>go get go.opentelemetry.io/otel \<br>    go.opentelemetry.io/otel/trace \<br>    go.opentelemetry.io/otel/sdk \<br>    go.opentelemetry.io/otel/sdk/resource \<br>    go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc</pre><p>Após instalado os pacotes vamos iniciar a implementação do nosso Provider, nesse meu exemplo vou usar como base uma aplicação que esteja utilizando o gin como servidor http ao qual preciso instrumentar as requisições, iniciamos com a configuração do TraceProvider dentro de um arquivo chamado main.go iniciando com o bloco de código abaixo:</p><pre>package main<br><br>import (<br> &quot;context&quot;<br><br> &quot;go.opentelemetry.io/otel&quot;<br> &quot;go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc&quot;<br> &quot;go.opentelemetry.io/otel/propagation&quot;<br> &quot;go.opentelemetry.io/otel/sdk/resource&quot;<br> sdktrace &quot;go.opentelemetry.io/otel/sdk/trace&quot;<br> semconv &quot;go.opentelemetry.io/otel/semconv/v1.10.0&quot;<br>)<br><br>func setupOpenTelemetry(environment string, serviceName string) (*sdktrace.TracerProvider, error) {<br> var tp *sdktrace.TracerProvider<br> var err error<br> ctx := context.Background()<br><br> tp, err = setupProvider(ctx, environment, serviceName)<br> if err != nil {<br>  return nil, err<br> }<br> otel.SetTracerProvider(tp)<br> otel.SetTextMapPropagator(<br>  propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}),<br> )<br><br> return tp, nil<br>}<br><br>func setupProvider(ctx context.Context, environment string, serviceName string) (*sdktrace.TracerProvider, error) {<br> res, err := resource.New(ctx,<br>  resource.WithFromEnv(),<br>  resource.WithAttributes(<br>   semconv.ServiceNameKey.String(serviceName),<br>  ),<br> )<br><br> if err != nil {<br>  return nil, err<br> }<br><br> var traceExporter sdktrace.SpanExporter<br><br> // Configuração do Exporter em gRPC, que é um requisito padrão em nossa arquitetura com OpenTelemetry<br> traceExporter, err = otlptracegrpc.New(ctx)<br> if err != nil {<br>  return nil, err<br> }<br><br> bsp := sdktrace.NewBatchSpanProcessor(traceExporter)<br><br> tp := sdktrace.NewTracerProvider(<br>  // Configuração do SampleRate via ParentBasedTraceIDRatio<br>  sdktrace.WithSampler(sdktrace.AlwaysSample()),<br>  sdktrace.WithResource(res),<br>  sdktrace.WithSpanProcessor(bsp),<br>  sdktrace.WithResource(resource.NewWithAttributes(<br>   semconv.SchemaURL,<br>   semconv.ServiceNameKey.String(serviceName),<br>   semconv.DeploymentEnvironmentKey.String(environment),<br>  )),<br> )<br><br> return tp, nil<br>}</pre><h3>Integrando o OpenTelemetry ao Gin</h3><p>Após a criação das funções de configuração do OpenTelemetry, o que vai faltar é a configuração do server que vamos integrar o nosso OpenTelemetry, abaixo tenho uma demonstração utilizando o Gin de como podemos fazer integrar facilmente em nosso framework ou pacote, onde primeiro vamos precisar instalar os pacotes e depois implementar nosso código:</p><pre>go get github.com/gin-gonic/gin \<br> go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin</pre><p>Após instalarmos os pacotes necessários podemos seguir com a implementação:</p><pre>import (<br> &quot;context&quot;<br> &quot;log&quot;<br><br> &quot;github.com/gin-gonic/gin&quot;<br> &quot;go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin&quot;<br>)<br><br>func main() {<br> // Inicializamos o TracerProvider para que possamos integrar ao nosso GracefulShutdown<br> tp, err := setupOpenTelemetry(&quot;local&quot;, &quot;go-otel-basic&quot;)<br><br> if err != nil {<br>  log.Fatal(err)<br> }<br><br> defer func() {<br>  if err := tp.Shutdown(context.Background()); err != nil {<br>   log.Printf(&quot;Error shutting down tracer provider: %v&quot;, err)<br>  }<br> }()<br><br> r := gin.New()<br> // Injetamos o Middleware que vai fazer a instrumentação dos nossos requests<br> r.Use(otelgin.Middleware(&quot;http-server&quot;))<br><br> r.GET(&quot;/up&quot;, func(c *gin.Context) {<br>  c.JSON(200, gin.H{<br>   &quot;hello&quot;: &quot;world&quot;,<br>  })<br> })<br><br> r.Run()<br>}<br><br>....</pre><p>Para que possamos testar essa aplicação vamos precisar configurar localmente um Collector do OpenTelemetry, onde temos várias alternativas como o Jaeger, SigNoz, Grafana Tempo, etc. Mas para simplificar nossa implementação local vamos seguir com o Jaeger configurado via docker compose, onde vamos precisar criar um arquivo com o nome compose.yml e adicionar o conteúdo conforme demonstrado abaixo:</p><pre>services:<br>  jaeger:<br>    image: jaegertracing/all-in-one:latest<br>    environment:<br>      - COLLECTOR_OTLP_ENABLED=true<br>    ports:<br>      - &quot;16686:16686&quot;<br>      - &quot;4317:4317&quot;</pre><p>Após criação do arquivo do Docker Compose, conseguimos executar nosso pequeno projeto com o seguinte comando abaixo:</p><pre>docker compose up -d<br><br>OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 go run main.go<br><br># Chamada para a API criada<br>curl --location &#39;&lt;http://localhost:8080/up&gt;&#39;</pre><p>Pronto, agora você pode acessar o <a href="http://localhost:16686/">http://localhost:16686</a> onde vai poder visualizar o Trace gerado pela a requisição realizou para o serviço que subiu, <a href="https://github.com/vitorhugoro1/go-basic-open-telemetry">neste link você tem acesso ao código final da implementação</a>, e na imagem abaixo um exemplo de como o Jaeger vai mostrar o Trace:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4_lavZaWjICCzyAaBWxFLQ.png" /><figcaption>Como o Jaeger mostra os Traces</figcaption></figure><h3>Próximos passos</h3><p>Se você chegou aqui e realizou todos os passos que descrevi em uma aplicação existente deve estar se perguntando agora “mas x integração minha não instrumentou”, bom neste caso a integração da instrumentação para o banco de dados, client http, e quaisquer outra ferramenta vai precisar integrar manualmente com a sua ferramenta ou buscar uma integração oficial como é o caso dessas abaixo:</p><ul><li><a href="https://github.com/XSAM/otelsql">database/sql</a></li><li><a href="https://github.com/open-telemetry/opentelemetry-go-contrib/tree/main/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo">mongo-driver</a></li><li><a href="https://github.com/open-telemetry/opentelemetry-go-contrib/tree/main/instrumentation/google.golang.org/grpc/otelgrpc">grpc</a></li><li>net/http (<a href="https://github.com/open-telemetry/opentelemetry-go-contrib/tree/main/instrumentation/net/http/httptrace/otelhttptrace">client</a>, <a href="https://github.com/open-telemetry/opentelemetry-go-contrib/tree/main/instrumentation/net/http/otelhttp">server</a>)</li></ul><p>Como pode ver cada biblioteca vai possuir uma integração da comunidade, ou caso a mesma não possua, você pode realizar essa instrumentação de forma manual, o que te garante certa praticidade. É isso se tiverem dúvidas quanto a essas implementações ou como podemos evoluir ela ainda mais, comentem que eu posso fazer algo mais especifico ou complexo para mostrar de exemplo.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4376b23c3484" width="1" height="1" alt=""><hr><p><a href="https://medium.com/fretecom-tech/tracing-no-golang-com-o-opentelemetry-4376b23c3484">Tracing no Golang com o OpenTelemetry</a> was originally published in <a href="https://medium.com/fretecom-tech">Frete.com Tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A Observabilidade no PHP com OpenTelemetry]]></title>
            <link>https://medium.com/fretecom-tech/a-observabilidade-no-php-com-opentelemetry-0fada412a047?source=rss----a43f9b9429a---4</link>
            <guid isPermaLink="false">https://medium.com/p/0fada412a047</guid>
            <category><![CDATA[engenharia]]></category>
            <category><![CDATA[opentelemetry]]></category>
            <category><![CDATA[php]]></category>
            <category><![CDATA[laravel]]></category>
            <dc:creator><![CDATA[Vitor Hugo R Merencio]]></dc:creator>
            <pubDate>Wed, 17 Jul 2024 21:53:06 GMT</pubDate>
            <atom:updated>2024-07-17T21:53:06.883Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*qEdBXHw-gQYEs14M" /><figcaption>Photo by <a href="https://unsplash.com/@benofthenorth?utm_source=medium&amp;utm_medium=referral">Ben Griffiths</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>Nos últimos meses este seja o principal item no qual tenho trabalhado lá na <a href="http://fretebras.com.br/">Fretebras</a>, como podemos melhorar nossas ferramentas de observabilidade tanto com material para que os times possam integrar de forma mais prática a telemetria, quanto com alternativas quando a aplicação legada não tem suporte as principais bibliotecas disponíveis no mercado. Hoje o <strong>foco dessa postagem é o PHP com OpenTelemetry</strong>, que é a principal linguagem da empresa ainda hoje, mesmo que ela não seja mais a linguagem da moda, alinhada com um bom framework (Laravel) e uma comunidade extremamente ativa é um canivete suíço quando se trata de velocidade de entrega.</p><h3>Como usar no PHP?</h3><p>Há mais de uma forma de utilizar o OpenTelemetry (<strong>Otel</strong>) no PHP, mas pelas experiências e testes que fiz essa ainda é a melhor opção que temos para utilizar o Otel dentro do ecossistema do PHP, que seria através da extensão do opentelemetry a qual pode ser instalada via pecl no servidor ou container que você esteja utilizando, abaixo mostro como pode ser feito</p><pre># Instala a extensão via pecl<br>pecl install opentelemetry<br><br>## Quando estiver utilizando via a imagem Docker do PHP, é necessário ativar a extensão após sua instalação<br>docker-ext-enable opentelemetry<br><br># Para verificar que o mesmo foi instalado<br>php -m | grep opentelemetry</pre><p>Após a instalação e ativação da extensão podemos fazer o próximo passo que é a instalação dos pacotes via composer. Nesta forma de integração vamos utilizar os pacotes mantidos pelo core team do Otel, o qual irá nós poupar bastante trabalho se fossemos realizar a integração manual. Para que possamos identificar o pacote que melhor se adeque a nossa necessidade, é necessário verificar alguns itens antes da instalação, sendo eles como:</p><ul><li>Qual banco de dados utilizamos?</li><li>Aplicamos alguma das seguintes PSR (PSR-3, PSR-15, PSR-14, PSR-15)</li><li>Qual método de exportação vamos utilizar, gRPC ou HTTP? Zipkin ou OTLP?</li></ul><p>Com essas perguntas respondidas será possível definir a instalação de alguns pacotes como o open-telemetry/opentelemetry-auto-psr15 em que conseguimos instrumentar automaticamente toda implementação da <a href="https://www.php-fig.org/psr/psr-15/">PSR-15</a>, outra muito importante é a open-telemetry/opentelemetry-auto-pdo onde temos a instrumentação das implementações do PDO (<a href="https://php.net/pdo">PHP Data Objects</a>) de forma automática sem a necessidade de customizações para o seu funcionamento, e por fim, uma outra integração bastante relevante é a com o Exporter que desejamos usar para enviar as informações para o nosso Collector neste caso, se não utilizar o gRPC, acredito que a melhor seja open-telemetry/exporter-otlp que é bastante agnóstica ao serviço de Collector que utilizarmos. Abaixo o comando para realizar a instalação:</p><pre>composer require open-telemetry/opentelemetry-auto-psr15 \\<br>	open-telemetry/opentelemetry-auto-pdo \\<br>	open-telemetry/exporter-otlp \\<br>	open-telemetry/gen-otlp-protobuf</pre><p>Para o funcionamento real da extensão e dos pacotes instalados é necessário configurarmos as variáveis de ambiente que são utilizadas pela extensão e seus pacotes para identificar como enviar as informações, abaixo um exemplo das variáveis de ambiente:</p><pre>OTEL_PHP_AUTOLOAD_ENABLED=true<br>OTEL_TRACES_EXPORTER=otlp<br>OTEL_METRICS_EXPORTER=none<br>OTEL_LOGS_EXPORTER=none<br>OTEL_PROPAGATORS=baggage,tracecontext</pre><h3>E quando tenho um framework?</h3><p>A instalação anterior tratava principalmente casos onde não temos nenhum Framework instalado em nossos projetos, o que não é algo muito recomendado atualmente tendo em vista a velocidade que uma comunidade de pessoas consegue tratar uma CVE (Common Vulnerabilities and Exposures) que possa atingir um grande framework como <strong>Laravel</strong> ou <strong>Symfony</strong>.</p><p>Como na <strong>Fretebras</strong> trabalhamos com Laravel, vou exemplificar como podemos fazer a instalação dos pacotes necessários para integrar ele de forma automática. Assim como os outros pacotes que mostrei antes é bastante simples, só é necessária a instalação de um pacote open-telemetry/opentelemetry-auto-laravel, neste pacote há bem mais ferramentas do que em outros pacotes que exemplifiquei por estarmos trabalhando em um framework.</p><pre>composer require open-telemetry/opentelemetry-auto-laravel</pre><p>Esse pacote inclui até o momento a instrumentação automática dos seguintes itens nativos do Laravel: Requests, Console/Command, Queue, Cache, ClientRequest, Exceptions, Log e as Queries, ufa acredito que seja quase tudo até o momento, mas, como um framework que está sempre se desenvolvendo pode ser que alguma funcionalidade muito nova ainda não tenha sua própria implementação dentro do pacote oficial, contudo, como o Otel é bastante flexível, ainda é possível fazer uma integração manual do seu pacote preferido.</p><p>Alguns pacotes famosos como mongodb/laravel-mongodb não funcionam automaticamente e necessitam de uma integração manual ou o uso do pacote open-telemetry/opentelemetry-auto-mongodb se quiser uma integração que não seja tão presa ao Laravel, mas ainda sim conseguir capturar as interações com o MongoDB sem maiores problemas.</p><h3>Complementando seu conhecimento</h3><p>Essa postagem foi mais para falar de uma forma mais ampla como podemos fazer para utilizar o OpenTelemetry no PHP, e consequentemente no framework mais famoso dele, o Laravel, entretanto isso é apenas uma pequena ponta de um grande Iceberg que é a <strong>Observabilidade</strong> no geral, quanto em aplicações PHP em que podemos ir para casos onde nossa aplicação gera métricas de uso e disponibilidade em tempo real utilizando o OpenTelemetry como agregador desses dados.</p><p>Como todo estudo ele nos gera diversas referências que estou listando aqui abaixo para complementar o conhecimento de quem leu até aqui:</p><ul><li><a href="https://opentelemetry.io/docs/languages/php/getting-started/">O guia oficial do OpenTelemetry para o PHP</a></li><li><a href="https://medium.com/fretebras-tech/observabilidade-instrumentando-uma-aplica%C3%A7%C3%A3o-laravel-com-opentelemetry-f341b5fdf6ce">Uma postagem de instrumentação manual no Laravel</a> (com ótimos exemplos de como podemos fazer isso manualmente)</li><li><a href="https://github.com/spatie/laravel-open-telemetry">Pacote experimental da Spatie para o Laravel</a></li><li><a href="https://github.com/keepsuit/laravel-opentelemetry/tree/main">E um ótimo pacote da KeepSuite que re-implementa de maneira mais customizável o pacote oficial da Otel</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=0fada412a047" width="1" height="1" alt=""><hr><p><a href="https://medium.com/fretecom-tech/a-observabilidade-no-php-com-opentelemetry-0fada412a047">A Observabilidade no PHP com OpenTelemetry</a> was originally published in <a href="https://medium.com/fretecom-tech">Frete.com Tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>