3 pontos por GN⁺ 2024-11-04 | 2 comentários | Compartilhar no WhatsApp
  • Depois de avaliar várias stacks, o aplicativo para preencher declarações aduaneiras acabou usando o familiar Ruby on Rails na implementação inicial, pois parecia permitir criar funcionalidades muito mais rápido do que as novas opções
  • A “version 2”, feita com backend em Rust e frontend em SvelteKit, era rápida e bonita, mas tinha apenas cerca de 10% das funcionalidades necessárias, e o trabalho de infraestrutura acabou ficando maior do que o código de negócio
  • A migração gradual exigia integrar por conta própria roteamento, acesso a dados, permissões, rastreamento de alterações, busca, jobs em segundo plano, pub/sub, e-mail, OAuth 2.0, SMTP/POP, armazenamento de objetos e geração de PDFs, quase sem aumentar o valor para os usuários
  • Foi decidido reverter para Rails apenas as partes migradas para a nova stack; a situação de manter sozinho um app Rails, o estresse de não atender às demandas dos usuários e a complexidade de corrigir várias partes ao mesmo tempo influenciaram a decisão
  • Após o retorno ao Rails, o ritmo de desenvolvimento de funcionalidades, melhorias nas funcionalidades existentes, atualizações de UI e correções de bugs voltou a acelerar; as limitações de Ruby/Rails passaram a ser tratadas com testes, refatoração, Turbo e cache, escolhendo focar no desenvolvimento do produto

Rails como escolha inicial temporária

  • Em 2022, ao começar a criar um aplicativo para preencher declarações aduaneiras, foram avaliadas várias linguagens e frameworks, como .NET/F#, Go, Rust e React, com código escrito de fato em algumas delas
  • A escolha final foi Ruby on Rails
    • Era uma ferramenta já conhecida e parecia permitir avançar 10 vezes mais rápido do que as opções que exigiam aprendizado novo
  • Na época, a implementação em Rails era mais próxima de uma prova de conceito temporária, e o plano era reescrevê-la depois em outra linguagem que não fosse tão propensa a erros quanto Ruby
  • O aplicativo em Rails rapidamente ganhou funcionalidades, e os colegas gostaram do resultado
    • Foi possível criar rapidamente recursos que aumentavam a produtividade
    • Situações de erro que no início aconteciam com frequência puderam ser prevenidas por código

Onde a “version 2” com Rust e SvelteKit travou

  • A “version 2” escolheu Rust no backend e SvelteKit no frontend
  • A versão inicial era bonita e rápida, mas tinha apenas cerca de 10% das funcionalidades necessárias
  • Como Rust não tinha um framework integrado comparável ao Rails, foi preciso escrever mais código de base e de integração do que lógica de negócio
  • Ficou claro que, para criar uma version 2 completa, seria necessário parar totalmente o desenvolvimento da version 1, e a version 2 acabou sendo descartada
  • A experiência confirmou que uma reescrita pode levar um tempo semelhante ao que foi necessário para criar o aplicativo original pela primeira vez

A complexidade operacional criada pela migração gradual

  • A vontade de colocar Rust no aplicativo continuou, por causa da tipagem forte, do desempenho rápido e da confiança gerada quando a compilação é bem-sucedida
  • Mesmo usando Ruby por mais de 4 anos, problemas em runtime continuavam acontecendo
    • Principalmente valores null e exceções não tratadas
    • Depois do deploy, os problemas em produção eram corrigidos rapidamente observando a ferramenta de relatórios de erro
  • No frontend em Rails, incomodava o fato de a lógica e o markup ficarem espalhados por vários arquivos; em Svelte, era fácil reunir a lógica relacionada em um único arquivo
  • O plano passou a ser escrever lentamente o novo frontend em SvelteKit e o novo backend em Rust, substituindo também as partes existentes em Rails de forma gradual até remover Rails por completo
  • No início, a migração das páginas de “index” com listas de registros e filtros para a nova stack avançou rapidamente
    • A nova stack funcionava rápido, e as partes em Rust quase não tinham problemas em runtime
    • As regras de deploy para decidir quais rotas deveriam ir para Rails, Rust ou SvelteKit ficaram complexas
    • Essa complexidade foi aceita como um estado temporário até a migração completa

Trabalho técnico maior do que o valor para o usuário

  • Com o tempo, novas funcionalidades úteis continuaram sendo adicionadas no lado Rails, e as funcionalidades existentes também ficaram mais profundas e sofisticadas
  • Várias refatorações foram feitas para corrigir suposições iniciais incorretas, e concluiu-se que um esquema de banco de dados bem projetado ajudaria qualquer versão
  • Como a UI em Rails seria substituída em breve, foi decidido não dedicar mais muito tempo a ela
  • Alguns meses depois, o negócio teve sucesso suficiente para contratar um desenvolvedor em tempo integral, e havia a expectativa de que a migração para Rust acelerasse
  • Na prática, muito tempo na nova stack foi gasto escrevendo abstrações e mecanismos
    • Acesso ao banco de dados
    • Controle de permissões para que usuários vissem apenas os dados permitidos
    • Rastreamento de todas as alterações
    • Busca e filtros
    • Imposição de restrições por meio do sistema de tipos
  • Em seguida, a lista de funcionalidades necessárias também continuou crescendo
    • Jobs em segundo plano
    • Sistema de pub/sub para atualizações no cliente
    • Recursos de e-mail
    • OAuth 2.0 para conexão com serviços externos
    • SMTP e POP
    • Armazenamento de objetos
    • Geração de PDFs
  • Havia bibliotecas Rust suficientes, mas o trabalho de escolher as bibliotecas adequadas e conectar tudo era grande
  • Aumentaram as discussões sobre detalhes de implementação técnica, como a escolha de ORM em Rust e o momento de lançamento e as melhorias do Svelte 5
  • Depois de cerca de 1 ano escrevendo Rust e Svelte, ficou claro que os usuários quase não se beneficiavam desse trabalho
    • Algumas telas da nova stack eram fluidas e rápidas, mas não eram as partes que originalmente causavam problemas
    • As páginas mais usadas ainda rodavam em Rails e tinham 10 vezes mais funcionalidades do que a “versão beta” da nova stack
    • Dias ou semanas eram gastos em trabalhos que não adicionavam valor direto

A decisão de voltar para Rails

  • Foi decidido reescrever em Rails apenas as partes que estavam rodando na nova stack
  • Não era reescrever o aplicativo inteiro, mas sim levar de volta para Rails as partes migradas para Rust/SvelteKit
  • A decisão foi difícil, mas racional
    • Muito tempo e muito código já haviam sido investidos
    • Também havia a possibilidade de que membros da equipe não gostassem de Ruby ou Rails
    • Felizmente, a equipe entendeu a decisão e estava aberta a aprender algo novo
  • Os pontos fortes do Rails são incluir muitas funcionalidades por padrão, ter um ecossistema de bibliotecas maduro e estável, e produzir código conciso graças a convenções e a um design opinativo
  • Rails ajuda a focar em criar o produto e reduz o peso de escrever manualmente o código de suporte de baixo nível

Lições restantes após o retorno ao Rails

  • A tentativa com Rust e Svelte não foi um desperdício completo
    • Ao aprender novas linguagens e frameworks, surgiram insights e ideias úteis também em outras linguagens
    • Escrever aplicações web é uma sequência de escolhas e trade-offs
    • É preciso aceitar as falhas de um framework ou investir em implementações próprias, avaliando se esse investimento trará vantagem competitiva e retorno de longo prazo
  • O trabalho de voltar para Rails foi em grande parte simples e também reduziu o estresse
  • Nas últimas semanas, voltou a sensação de produtividade no desenvolvimento de novas funcionalidades, melhorias em funcionalidades existentes, atualizações de UI e correções de bugs
  • As limitações de Ruby e Rails foram aceitas como condições a considerar ao escrever código
    • Se houver muitos problemas em runtime, é preciso testar mais
    • Código espaguete difícil de manter costuma ser criado mais facilmente por desenvolvedores do que por uma linguagem ou framework
    • É preciso dedicar mais tempo a refatoração e organização do código
    • Se for difícil escalar Rails, provavelmente também será difícil escalar outras opções
    • Rails é rápido o suficiente, e há formas de contornar gargalos com Turbo e cache
  • Do ponto de vista do usuário, a stack em Rust não era perceptivelmente mais rápida
  • A página mais pesada em Rails tem P95 duration de 338 ms; ainda há espaço para melhoria, mas ela parece rápida o suficiente
  • Não existe linguagem ou framework perfeito; é necessário conhecer os pontos fortes e fracos e saber lidar com eles
  • Rails é um dos frameworks muito bons para quem quer focar em criar um bom produto, e outros frameworks mantidos há muito tempo e com muitos contribuidores ativos também podem ser escolhas igualmente boas

2 comentários

 
lemonmint 2024-11-04

Reescreva em Ruby
Reescreva em Rails

 
GN⁺ 2024-11-04
Opiniões no Hacker News
  • Vejo Rails muito à frente de outras stacks para criar aplicações web, incluindo Django ou Laravel
    Ele já resolveu todas as partes tediosas, sabe quais requisitos são necessários e atrapalha menos na hora de criar uma aplicação real. Em 15 anos desenvolvendo apps web, usei Meteor, Remix, Next.js, Node.js baseado em Express etc., mas sinto que desenvolvimento web já é um problema bastante resolvido. Do lado do JavaScript, impulsionados pelo dinheiro das grandes empresas, os desenvolvedores parecem estar mais brincando com brinquedos novos; Rust ou Go são bons para desenvolvimento de sistemas e infraestrutura de deploy, mas acho que não faz sentido enfiá-los à força no desenvolvimento web

    • Mesmo não sendo uma stack da moda, sinto que Spring Framework/Spring Boot é, no geral, a melhor stack de aplicações web
      Integra-se facilmente a inúmeras bibliotecas Java, e o fato de ser uma linguagem de tipagem estática ajuda em várias situações. Só o data binding baseado em convenções via Jackson já era excelente havia muito tempo; em linguagens de tipagem dinâmica, dá muito a impressão de que esse tipo de coisa foi acrescentado depois
    • Esses frameworks são como uma via expressa para começar projetos, porque você não precisa tomar grandes decisões
      Mas, depois de se aprofundar em Rails, Django ou Laravel, pode ficar complicado se precisar fazer algo que fuja das convenções. Como você não escreveu o código de cola diretamente, muitas vezes surge uma lacuna de entendimento, mais do que uma barreira técnica. Ainda assim, se você aprender bem o funcionamento interno, só de ler o código dá para economizar uma quantidade enorme de tempo de programação. Claro que, na prática, não fazemos isso; reescrever tudo é divertido demais, então acabamos curtindo novas tentativas mesmo quando elas não fazem sentido para o negócio
    • Discordo. Rails é ótimo em recursos e produtividade, mas não é uma solução universal
      Não confie só na minha opinião; basta olhar os dados: https://www.youtube.com/watch?v=Qp9SOOtgmS4
      Se você usar Rails em um serviço sem saber ao certo o número de usuários ou de requisições por segundo, pode economizar no custo de lançamento, mas acabar gastando mais com servidores
    • Como alguém que usou AOLServer e esteve, em 1999, numa startup que fazia algo parecido com Rails em Tcl, não concordo muito
      A demo do Rails não me pareceu tão atraente, exceto por mostrar como sorte, adoção e atenção são importantes. Ainda hoje não vejo bem o valor, e escolheria Spring ou ASP.NET. Os fundadores daquela startup baseada em Tcl depois criaram a OutSystems, uma das poucas ferramentas RAD que tiveram sucesso no desenvolvimento web em escala empresarial e um dos casos de sucesso da indústria de TI portuguesa
    • Essa conversa se aplica em grande parte também ao Django
      Antigamente eu preferia mais Rails, mas, com o enorme crescimento de Python nos últimos mais de 10 anos, Django se tornou uma opção mais prática. Desenvolvedores de machine learning e ciência de dados já estão acostumados com Python, e a documentação do Django é tão boa que, se necessário, dá para se tornar produtivo em pouquíssimo tempo. Vi isso diretamente em vários projetos
      Django também reduz as decisões e discussões sobre a estrutura da aplicação em comparação com bibliotecas menos opinativas ou microframeworks. A comunidade Django/FLOSS também é muito mais ativa do que eu esperava, e as interações foram agradáveis. Só gostaria que houvesse geradores como os do Rails e recursos embutidos para seed de dados, por exemplo algo como rake db:seed
  • Uso Rails há 17 anos, ainda gosto dele e também o prefiro para frontend
    Ele oferece todas as opções para separar código sem transformar o topo dos arquivos numa lista desnecessariamente longa de includes, como se vê em apps Angular. Não vejo desvantagem em separar lógica e markup no frontend. Dá para testar a lógica de forma independente e, normalmente, quando se procura um bug, dá para saber se o problema está no markup ou na lógica, o que facilita o rastreamento. Também escrevi muitos métodos Ruby com mais de 200 linhas, mas isso foi realmente culpa minha, e um sistema de testes unitários mais granular poderia evitar isso

    • Outra forma de evitar listas longas de includes é tornar tudo global
      Não tenho certeza de que isso seja uma vantagem. Imports podem ocupar bastante espaço, mas mostram explicitamente como tudo se conecta. Se atrapalhar, é só recolher no IDE
  • Gosto do texto por ser realista. A frase principal é algo para deixar sempre ao lado da cama e reler
    “Curiosamente, as coisas em Ruby e Rails que antes me irritavam agora incomodam muito menos. Aceitei que não existe linguagem nem framework perfeito. Basta conhecer e lidar com seus pontos fortes e fracos”

    • Gosto do texto por ser pragmático. Parece uma boa introdução para ler antes de reescrever alguma coisa
    • Muitas vezes sinto que pessoas que reclamam de frameworks têm uma visão de mundo perfeita da qual não querem abrir mão
      Em muitos casos, elas não entendem o framework o suficiente e não sabem por que ele é como é. Mas é muito mais eficiente conhecer as limitações e trabalhar junto com o framework. É preciso investir tempo para entender direito as ferramentas que você usa
    • Essa frase vem no contexto desta tentativa
      “Nesse meio-tempo, comecei a criar a ‘versão 2’ da aplicação. Escolhi Rust como linguagem de backend e SvelteKit para o frontend.”
      Como a v1 começou em Rails, leio o texto como um alerta para ter cuidado ao abandonar um framework que já funciona bem
    • Para mim, essa também foi a percepção mais importante. Agora sou muito mais feliz por não ficar pensando o tempo todo coisas como “se ao menos eu tivesse enums de Rust!”
  • Rust é uma linguagem criada por pessoas muito inteligentes, mas parece que o foco está mais em teoria de tipos e em economizar algumas alocações do que em permitir que desenvolvedores de campo como eu criem algo útil e consistente
    Uso Rust desde 2020 e, mesmo assim, quando leio posts no blog do Rust, literalmente não entendo metade

    • Você poderia compartilhar um exemplo do que parece inconsistente em Rust? Pelo contrário, acho que é uma linguagem pequena, bem projetada e muito consistente
      Especialmente quando comparada a grandes linguagens mainstream. Por exemplo, traits cobrem os casos de uso de classes abstratas e concepts em C++, além de classes abstratas e protocolos em Python. Também não há inconsistência entre usar exceções ou retornar valores para tratar erros. Existem panics, mas são usados como assertions. Se if-else é uma expressão, também não há necessidade de um operador ternário separado
    • Rust é uma linguagem de sistemas e, nesse domínio, é excelente. Comparando com linguagens da mesma família, posts sobre C++ parecem muito menos consistentes
      Há uma tendência recente de querer usar Rust para tudo, mas ela não foi criada para aplicações CRUD. No fim, este texto é mais um estudo de caso de “use a ferramenta certa para o trabalho; a linguagem de que você gosta no tempo livre ou a linguagem da moda não é necessariamente a mais adequada”
    • Rust é essencialmente um substituto do C++
      Se você pode arcar com um garbage collector, não precisa se preocupar com gerenciamento de memória usando Rust ou C++. Há pessoas que promovem Rust como uma linguagem de programação de propósito geral, e teoricamente isso está certo, mas C++ também era promovida assim nos anos 90. Minimizar alocações de memória geralmente é exatamente a característica que se busca ao escolher Rust ou C++
  • É difícil escrever rapidamente uma grande aplicação web em Rust
    Uma aplicação grande escrita em Rails pode não rodar rápido, e também pode ter muitos bugs ou ser difícil de manter e escalar. Opções como Java, C# e Go talvez não pareçam sexy ou estilosas, mas podem entregar trabalho quase tão rápido quanto Rails e executar em velocidades próximas às de Rust. Normalmente também resultam em projetos com menos bugs e mais fáceis de manter e escalar

    • Não dá para dizer seriamente que uma linguagem de sistemas de baixo nível como Go é tão produtiva quanto Rails, um framework de alto nível para aplicações CRUD
      Por mais que alguém odeie Rails, isso é parecido com dizer que dá para criar um blog em C++ mais rápido do que com WordPress
    • Acho uma generalização excessiva dizer que “uma aplicação grande escrita em Rails pode não ser rápida, ter muitos bugs e ser difícil de manter e escalar”
      Se for feita com cuidado, isso não é necessariamente verdade. Já vi muitos apps Rails grandes, com pouquíssimos bugs, estáveis e fáceis de modificar, e também já vi apps Rails e Go bagunçados. Certas linguagens ou frameworks podem facilitar cair nesse estado, mas tudo é possível, e há trade-offs que tornam as coisas difíceis por outros motivos. Com disciplina, dá para fazer qualquer coisa funcionar
    • Também é possível criar aplicações grandes em Rails que sejam rápidas, tenham poucos bugs e não sejam difíceis de manter e escalar. Há muitos casos reais
      Com desenvolvedores de nível semelhante, Rails permite desenvolver mais rápido do que Java, C# ou Go na maioria das funcionalidades de aplicações web. Na minha experiência, chegou a ser até 3 vezes mais rápido. Já trabalhei tanto com grandes monólitos quanto com arquiteturas orientadas a serviços, e também trabalhei com equipes Java. Em certo momento, até introduzi Go em uma organização Rails por acreditar nisso
      No fim, o problema real era que MVC não serve para tudo e que namespaces não estavam sendo usados com rigor suficiente. Corrigindo isso em áreas pequenas e bem direcionadas, Rails é perfeitamente adequado
    • Nunca usei Basecamp, mas acredito que o produto seja bem escrito e bom
      Portanto, acho que, com gestão suficiente, boa arquitetura e manutenção, também é possível fazer isso em Rails
  • Recentemente comecei a testar AdonisJS como alternativa em TypeScript ao Rails
    Se você tem interesse em um “Rails em TypeScript”, recomendo experimentar. Ele parece muito mais próximo de Rails ou Laravel do que as stacks TypeScript mais populares, e segue uma abordagem batteries included, em vez de fazer você escolher cada parte da stack por conta própria ou confiar em templates de terceiros

    • Eu tinha curiosidade sobre o AdonisJS, e este comentário me deu vontade de experimentar
      Ultimamente tenho me sentido atraído por frameworks backend batteries included como Rails, Laravel e Django, mas, como usei TypeScript durante praticamente toda a minha carreira, é mais fácil continuar nele do que mudar agora. Tenho curiosidade sobre suas impressões ao testá-lo e se dá para compará-lo com os outros frameworks mencionados
    • Eu estava pensando em experimentar o AdonisJS, então é uma boa notícia
      Em um projeto pessoal recente, escolhi NestJS, mas logo conheci o Adonis e estou pensando seriamente em migrar
  • Tecnologias como Next.js e SvelteKit são populares entre desenvolvedores JavaScript porque aliviam o problema da experiência ruim de desenvolvimento backend no Node.js
    Criar um backend em TypeScript parece parecido com fazê-lo em C#, mas sem as vantagens de uma linguagem bem projetada. É uma experiência muito diferente de criar um backend em Rails. Desenvolvedores Node.js também estão começando a perceber que não é só uma questão de ecossistema e ferramentas; frameworks que oferecem uma solução completa para criar aplicações são importantes. É exatamente nesse ponto que Next.js e SvelteKit ajudam, e foi também nesse ponto que Rails inovou em 2006

    • Gosto de TypeScript. Fico curioso para saber em que sentido ela não seria uma linguagem bem projetada em comparação com C#
    • Comparar Next.js ou Remix com Rails, Django e Laravel é forçar a barra
      A comparação com Rails é simplesmente o fato de poder executar código no backend? Onde estão o acesso ao banco de dados, migrações, ORM, filas, tarefas agendadas, validação, tradução, autenticação e suporte a autorização? A única coisa em que o Next.js ajuda é empurrar as pessoas para a plataforma Vercel
    • Se tivesse que escolher entre C# e TypeScript, eu escolheria de bom grado C# no backend
      No frontend, prefiro TypeScript a JavaScript
  • Os comentários dizendo que Rust não é muito bom para backend web me deixam um pouco confuso
    Tive a experiência oposta criando principalmente serviços pequenos como beampaint.com ou probablyup.net. Ao criar apps maiores, a complexidade pode aumentar bastante — e provavelmente aumenta mesmo —, mas Rust é excelente para dar a confiança necessária em uma base de código grande

    • Será que a dificuldade não aumenta drasticamente no momento em que você passa a precisar de mais recursos na camada de framework?
      Frameworks mais antigos já trazem essas coisas por padrão, mas, pela idade, é menos provável que os frameworks de Rust tenham isso. É só imaginar os recursos faltantes passando de dois para seis
  • Este texto parece quase a história da minha vida
    Eu usava Rails e, em 2013, lancei um app SaaS escrito em Go, mas tive que descobrir tudo por conta própria. Migrações, camada de banco de dados, pipeline de assets, deploy, validação, logging, pagamentos etc.; eram muitas coisas que o Rails simplesmente fornecia

    • Ainda assim, é bem provável que essa experiência tenha feito de você um desenvolvedor mais experiente
    • Estão comparando uma linguagem de programação de baixo nível, voltada a sistemas e com apenas o esqueleto básico, com um framework de altíssimo nível sobre uma linguagem de altíssimo nível
      Não gosto de Go, mas aqui o culpado não é Go. Usar Go para uma aplicação CRUD foi uma decisão horrível desde o início
  • Com Django, a sensação é parecida
    Problemas de desempenho vinham muito mais frequentemente das consultas ao banco de dados do que do próprio Django

    • Quase sempre as consultas ao banco de dados vão ser o que mais demora, mas já vi engenheiros demais que nem fazem otimizações básicas como criar índices
      Entre otimizações simples que costumam ser esquecidas estão criar índices no banco de dados, delegar mais lógica ao banco em vez de ao código — por exemplo, usando pipelines do MongoDB ou índices parciais do Postgres —, ativar compressão Brotli/zstd para a maioria das requisições, fazer cache de objetos ou requisições com um cache em memória como Redis e configurar cabeçalhos adequados de controle de cache. No frontend, também são necessárias otimizações como usar canvas em vez de renderizar milhares de objetos como nós do DOM. Vi esse problema recentemente em uma aplicação de mapas