Reescrever em Rails
(dirkjonker.bearblog.dev)- 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
Reescreva em Ruby
Reescreva em Rails
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
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
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
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
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
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:seedUso 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 issoincludesé tornar tudo globalNã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”
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
“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
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
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 separadoHá 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”
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
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
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
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
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
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
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
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
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
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
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
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