Reescribirlo en Rails
(dirkjonker.bearblog.dev)- Tras evaluar varios stacks, la aplicación para preparar declaraciones aduaneras terminó usando el conocido Ruby on Rails para la implementación inicial, porque permitía crear funcionalidades mucho más rápido, en la práctica, que las opciones nuevas
- La “version 2”, hecha con backend en Rust y frontend en SvelteKit, era rápida y se veía bien, pero solo tenía alrededor del 10% de las funcionalidades necesarias, y el trabajo de plomería terminó siendo mayor que el código de negocio
- La migración gradual obligaba a conectar manualmente ruteo, acceso a datos, permisos, seguimiento de cambios, búsqueda, trabajos en segundo plano, pub/sub, email, OAuth 2.0, SMTP/POP, almacenamiento de objetos y generación de PDF, con muy poco aumento de valor para el usuario
- Se decidió volver a Rails solo en las partes migradas al stack nuevo; influyeron la situación de mantener una app Rails en solitario, el estrés de no poder cubrir las necesidades de los usuarios y la complejidad de tener que corregir varias áreas a la vez
- Tras volver a Rails, recuperaron velocidad en el desarrollo de funciones, mejoras de funcionalidades existentes, actualizaciones de UI y corrección de bugs; eligieron enfocarse en desarrollar el producto, manejando las desventajas de Ruby/Rails con pruebas, refactorización, Turbo y caché
El punto de partida: elegir Rails como opción temporal
- En 2022, al empezar a crear una aplicación para preparar declaraciones aduaneras, se evaluaron varios lenguajes y frameworks como .NET/F#, Go, Rust y React, e incluso se escribió código con ellos
- La elección final fue Ruby on Rails
- Ya era una herramienta familiar y, en la práctica, permitía avanzar unas 10 veces más rápido que las opciones que había que aprender desde cero
- En ese momento, la implementación en Rails era más bien una prueba de concepto temporal, y el plan era reescribirla más adelante en otro lenguaje que no fuera tan propenso a errores como Ruby
- La aplicación Rails sumó funcionalidades rápidamente y a los compañeros también les gustó el resultado
- Se podían crear con rapidez funciones que aumentaban la productividad
- Situaciones de error que al principio ocurrían con frecuencia pudieron bloquearse mediante código
Dónde se estancó la “version 2” con Rust y SvelteKit
- Para la “version 2” se eligió Rust en el backend y SvelteKit en el frontend
- La versión inicial se veía bien y funcionaba rápido, pero solo tenía alrededor del 10% de las funcionalidades necesarias
- Rust no tenía un framework integrado comparable con Rails, así que había que escribir más código de base y de integración que lógica de negocio
- Quedó claro que, para crear una version 2 completa, había que detener por completo el desarrollo de la version 1, y finalmente la version 2 fue descartada
- Se confirmó que una reescritura podía tomar un tiempo similar al que llevó crear la aplicación original desde cero
La complejidad operativa que generó la migración gradual
- Seguía presente el deseo de incorporar Rust a la aplicación por sus tipos fuertes, su alto rendimiento y la confianza que da cuando la compilación pasa con éxito
- Aunque se había usado Ruby durante más de 4 años, los problemas en runtime también seguían apareciendo
- Principalmente, los problemas eran valores null y excepciones no manejadas
- Después de desplegar, se miraban las herramientas de reporte de errores y se corregían rápido los problemas en producción
- En el frontend de Rails, resultaba frustrante que la lógica y el markup quedaran dispersos en varios archivos, mientras que Svelte hacía más fácil agrupar la lógica relacionada en un solo archivo
- Se armó un plan para escribir lentamente el nuevo frontend en SvelteKit y el nuevo backend en Rust, reemplazar de forma gradual las partes existentes de Rails y, al final, eliminar Rails
- Al principio, la migración de páginas “index” con listas de registros y filtros al nuevo stack avanzó rápido
- El nuevo stack funcionaba con rapidez y la parte en Rust casi no tenía problemas en runtime
- Se volvieron complejas las reglas de despliegue para decidir qué rutas debían ir a Rails, Rust o SvelteKit
- Esa complejidad se aceptó como un estado temporal hasta completar toda la transición
El trabajo técnico que superó al valor para el usuario
- Con el tiempo, el lado Rails siguió sumando nuevas funcionalidades útiles, y las existentes se volvieron más profundas y sofisticadas
- Se hicieron varias refactorizaciones para corregir supuestos iniciales equivocados, y se concluyó que un esquema de base de datos bien diseñado ayuda a cualquier versión
- Como la UI de Rails iba a ser reemplazada pronto, se decidió no dedicarle mucho más tiempo
- Unos meses después, el negocio tuvo suficiente éxito como para contratar a un desarrollador de tiempo completo, y se esperaba que la transición a Rust se acelerara
- En la práctica, en el nuevo stack se dedicó mucho tiempo a escribir abstracciones y mecanismos
- Acceso a la base de datos
- Manejo de permisos para que los usuarios solo vieran los datos autorizados
- Seguimiento de todos los cambios
- Búsqueda y filtros
- Imposición de restricciones mediante el sistema de tipos
- Luego, la lista de funcionalidades necesarias siguió creciendo
- Trabajos en segundo plano
- Sistema pub/sub para actualizaciones del cliente
- Funcionalidad de email
- OAuth 2.0 para conectarse con servicios externos
- SMTP y POP
- Almacenamiento de objetos
- Generación de PDF
- Había suficientes librerías de Rust, pero el trabajo de elegir las adecuadas y conectarlas todas era grande
- Aumentaron las discusiones sobre detalles de implementación técnica, como la elección de un ORM para Rust o el momento de lanzamiento y las mejoras de Svelte 5
- Después de aproximadamente un año escribiendo Rust y Svelte, quedó claro que los usuarios casi no obtenían beneficio de ese trabajo
- Algunas pantallas del nuevo stack eran fluidas y rápidas, pero no eran las partes que originalmente causaban problemas
- Las páginas más usadas seguían funcionando en Rails y tenían 10 veces más funcionalidades que la “versión beta” del nuevo stack
- Se terminaba dedicando días o semanas a trabajo que no agregaba valor directo
La decisión de volver a Rails
- Se decidió reescribir en Rails solo las partes que ya estaban corriendo en el nuevo stack
- No era reescribir toda la aplicación, sino devolver a Rails las partes migradas a Rust/SvelteKit
- La decisión fue difícil, pero razonable
- Ya se había invertido mucho tiempo y código
- Existía la posibilidad de que a los miembros del equipo no les gustara Ruby o Rails
- Por suerte, el equipo entendió la decisión y estuvo abierto a aprender
- Las fortalezas de Rails son que incluye muchas funcionalidades de base, tiene un ecosistema de librerías maduro y estable, y su diseño con convenciones y opiniones fuertes permite mantener el código conciso
- Rails permite enfocarse en crear el producto y reduce la carga de escribir manualmente código de soporte de bajo nivel
Lecciones que quedaron tras volver a Rails
- El intento con Rust y Svelte no fue una pérdida total
- Al aprender nuevos lenguajes y frameworks, se obtuvieron ideas e insights útiles también en otros lenguajes
- Escribir aplicaciones web es una sucesión constante de elecciones y compromisos
- Hay que aceptar los defectos del framework o invertir en implementarlos por cuenta propia, y evaluar si esa inversión dará ventaja competitiva y recompensa a largo plazo
- El trabajo de volver a Rails fue, en buena parte, sencillo y también redujo el estrés
- En las últimas semanas se volvió a sentir productividad en el desarrollo de nuevas funcionalidades, la mejora de funciones existentes, las actualizaciones de UI y la corrección de bugs
- Las desventajas de Ruby y Rails se aceptan como condiciones a considerar al escribir código
- Si hay muchos problemas en runtime, hay que probar más
- El código espagueti difícil de mantener suele ser más fácil de crear por los desarrolladores que por el lenguaje o el framework
- Hay que dedicar más tiempo a la refactorización y a la organización del código
- Si Rails es difícil de escalar, probablemente otras cosas también sean difíciles de escalar
- Rails es suficientemente rápido, y hay formas de rodear los problemas con Turbo y caché
- Desde la perspectiva del usuario, el stack en Rust no era notablemente más rápido
- En Rails, la página más pesada tiene una duración P95 de 338 ms, y aunque hay margen de mejora, se siente suficientemente rápida
- No existe un lenguaje o framework perfecto; hace falta conocer sus fortalezas y debilidades, y saber cómo manejarlas
- Rails es uno de los muy buenos frameworks para quienes quieren enfocarse en crear un buen producto, y otros frameworks con larga trayectoria y muchos contribuidores activos también pueden ser opciones igual de buenas
2 comentarios
Reescríbelo en Ruby
Reescríbelo en Rails
Opiniones en Hacker News
Creo que Rails está muy por delante de otros stacks para crear apps web, incluso comparándolo con Django o Laravel.
Ya se encargó de las partes aburridas, entiende los requisitos necesarios y estorba menos al construir apps reales. En 15 años desarrollando apps web probé Meteor, Remix, Next.js, Node.js basado en Express, etc., pero siento que el desarrollo web ya es un problema bastante resuelto. En el mundo de JavaScript, impulsado por el dinero de las grandes empresas, los desarrolladores parecen más enfocados en jugar con juguetes nuevos; y Rust o Go son buenos para desarrollo de sistemas e infraestructura de despliegue, pero no tiene sentido forzarlos en el desarrollo web.
Se integra fácilmente con muchísimas bibliotecas de Java, y al ser un lenguaje con tipado estático ayuda en muchas situaciones. Solo el enlace de datos basado en convenciones mediante Jackson ya era excelente desde hace mucho tiempo, y en los lenguajes de tipado dinámico da mucho la impresión de que estas cosas se agregaron después.
Pero después de meterse a fondo en Rails, Django o Laravel, puede volverse complicado si hay que hacer cosas que se salen de las convenciones. Como uno no escribió el código de unión, muchas veces aparece más un vacío de comprensión que una barrera técnica. Aun así, si aprendes bien los internals, puedes ahorrarte una cantidad enorme de horas de programación solo leyendo el código. Claro que en la práctica no hacemos eso: reescribir todo es demasiado divertido, así que terminamos disfrutando probar algo nuevo aunque no tenga sentido para el negocio.
No se fíen solo de mi opinión; vean los datos: https://www.youtube.com/watch?v=Qp9SOOtgmS4
Si usas Rails para un servicio donde no sabes con certeza cuántos usuarios tendrá ni cuántas solicitudes por segundo manejará, puedes ahorrar costos al momento del lanzamiento, pero terminar pagando más en servidores.
La demo de Rails no me pareció particularmente atractiva, salvo por mostrar lo importante que puede ser la suerte para lograr adopción y atención. Incluso hoy no termino de ver su valor, y elegiría Spring o ASP.NET. Los fundadores de aquella startup basada en Tcl luego crearon OutSystems, una de las pocas herramientas RAD que triunfó en desarrollo web a escala empresarial y uno de los casos de éxito de la industria de TI en Portugal.
Antes prefería Rails, pero con el enorme crecimiento de Python durante más de la última década, Django se volvió una opción más práctica. Los desarrolladores de machine learning y ciencia de datos ya están familiarizados con Python, y la documentación de Django es tan buena que, si hace falta, pueden volverse productivos en muy poco tiempo. Lo vi directamente en varios proyectos.
Django también reduce las decisiones y discusiones sobre la estructura de la aplicación en comparación con bibliotecas menos dogmáticas o microframeworks. La comunidad Django/FLOSS también es mucho más activa de lo que esperaba, y la interacción fue agradable. Eso sí, me gustaría que tuviera generadores como Rails y funciones integradas para sembrar datos, por ejemplo algo como
rake db:seed.Llevo 17 años usando Rails, todavía me gusta y también lo prefiero para el frontend.
Te da todas las opciones para separar el código sin que la cabecera de los archivos termine siendo una lista innecesariamente larga de
includes, como se ve en las apps de Angular. No veo desventajas en separar la lógica y el marcado en el frontend. Puedes probar la lógica de forma independiente y, normalmente, cuando buscas un bug sabes si es un problema de marcado o de lógica, así que es más fácil rastrearlo. También escribí bastantes métodos de Ruby de más de 200 líneas, pero eso fue realmente culpa mía, y un esquema de pruebas unitarias más granular puede evitarlo.includeses hacer que todo sea global.No tengo claro que eso sea una ventaja. Las importaciones pueden ocupar bastante espacio, pero muestran explícitamente cómo se conecta todo. Si estorban, puedes plegarlas en el IDE.
Me gusta porque es un texto realista. La frase clave es de las que vale la pena tener siempre junto a la cama.
“Curiosamente, las cosas que antes me molestaban de Ruby y Rails ahora me molestan mucho menos. Acepté que no existe un lenguaje ni un framework perfectos. Hay que conocer sus fortalezas y debilidades y trabajar con ellas”.
En muchos casos no entienden lo suficiente el framework ni por qué está hecho de esa manera. Pero es mucho más eficiente conocer sus límites y trabajar junto con el framework. Hay que dedicar tiempo a entender bien las herramientas que uno usa.
“Mientras tanto, empecé a crear la ‘versión 2’ de la aplicación. Elegí Rust como lenguaje de backend y SvelteKit para el frontend”.
Como la v1 empezó con Rails, lo leo como un texto que advierte que hay que tener cuidado al abandonar un framework que ya funciona bien.
Rust es un lenguaje creado por gente muy inteligente, pero parece estar más enfocado en la teoría de tipos y en ahorrarse algunas asignaciones que en que desarrolladores de campo como yo construyan cosas útiles y coherentes
Aunque uso Rust desde 2020, cuando leo publicaciones del blog de Rust literalmente no entiendo la mitad
Sobre todo si se compara con lenguajes masivos y populares. Por ejemplo, los traits cubren los casos de uso de las clases abstractas y los concepts de C++, y de las clases abstractas y los protocolos de Python. Tampoco hay inconsistencia entre usar excepciones o devolver valores para manejar errores. Hay pánicos, pero se usan como aserciones. Si
if-elsees una expresión, tampoco hace falta un operador ternario aparteÚltimamente existe la tendencia de querer usar Rust para todo, pero no es un lenguaje hecho para aplicaciones CRUD. Este artículo, al final, se parece más a un estudio de caso de “usa la herramienta adecuada para el trabajo; el lenguaje que te gusta en tu tiempo libre o el lenguaje de moda no necesariamente es el correcto”
Si puedes permitirte un recolector de basura, no necesitas usar Rust ni C++ ni preocuparte por la gestión de memoria. Hay quienes promocionan Rust como un lenguaje de programación de propósito general, y en teoría tienen razón, pero en los 90 C++ también se promocionaba así. Minimizar las asignaciones de memoria suele ser precisamente la característica que uno busca al elegir Rust o C++
Es difícil escribir rápidamente una aplicación web grande en Rust
Una aplicación grande escrita en Rails puede no ejecutarse rápido, tener muchos bugs o ser difícil de mantener y escalar. Opciones como Java, C# o Go quizá no parezcan sexis ni geniales, pero pueden terminar el trabajo casi tan rápido como Rails y ejecutarse a velocidades cercanas a Rust. Por lo general, también resultan en proyectos con menos bugs y más fáciles de mantener y escalar
Por mucho que detestes Rails, esto es parecido a decir que puedes crear un blog más rápido en C++ que con WordPress
Si se construye con cuidado, no tiene por qué ser así. He visto muchas apps grandes en Rails con muy pocos bugs, estables y fáciles de modificar, y también he visto apps desastrosas tanto en Rails como en Go. Algunos lenguajes o frameworks pueden facilitar caer en ese estado, pero con cualquier cosa existe esa posibilidad y también hay compromisos que se vuelven difíciles por otros motivos. Con disciplina, se puede hacer que cualquier cosa funcione
Con desarrolladores de nivel similar, Rails se desarrolla más rápido que Java, C# o Go para la mayoría de las funciones de aplicaciones web. En mi experiencia, hasta 3 veces más rápido. He trabajado tanto con monolitos grandes como con arquitecturas orientadas a servicios, y también con equipos Java. En algún momento incluso introduje Go en una organización Rails con esa misma creencia
Al final, el verdadero problema era que MVC no encaja con todo y que no se habían usado los namespaces con suficiente rigor. Si se corrige eso en áreas pequeñas y bien enfocadas, Rails está más que bien
Por lo tanto, con suficiente gestión, buena arquitectura y mantenimiento, creo que también es posible con Rails
Hace poco empecé a probar AdonisJS como alternativa de TypeScript a Rails
Si te interesa “Rails en TypeScript”, recomiendo probarlo. Se siente mucho más cercano a Rails o Laravel que los stacks de TypeScript más populares, y adopta un enfoque de baterías incluidas en lugar de obligarte a elegir cada parte del stack por tu cuenta o confiar en plantillas de terceros
Últimamente me atraen los frameworks backend con baterías incluidas como Rails, Laravel y Django, pero como he usado casi exclusivamente TypeScript durante toda mi carrera, es más fácil seguir por ahí que cambiar ahora. Me da curiosidad saber cómo te fue al probarlo y si puedes compararlo con los otros frameworks que mencionaste
Para un proyecto personal reciente elegí NestJS, pero poco después conocí Adonis y ahora estoy considerando seriamente migrar
Tecnologías como Next.js y SvelteKit son populares entre desarrolladores JavaScript porque alivian el problema de la mala experiencia de desarrollo backend en Node.js
Hacer un backend con TypeScript se parece a hacerlo con C#, pero se siente como si le faltaran las ventajas de un lenguaje bien diseñado. Hay una gran diferencia frente a la experiencia de construir un backend con Rails. Los desarrolladores de Node.js también están empezando a darse cuenta de que no se trata solo de ecosistema y herramientas, sino de la importancia de frameworks que ofrezcan una solución integral para crear aplicaciones. Esa es la parte en la que Next.js y SvelteKit ayudan, y también donde Rails innovó en 2006
¿Estamos comparando el hecho de poder ejecutar código en el backend con Rails? ¿Dónde están el acceso a bases de datos, migraciones, ORM, colas, tareas programadas, validación, traducción, autenticación y soporte de autorización? Lo único en lo que Next.js ayuda es en empujar a la gente hacia la plataforma Vercel
Para frontend, prefiero TypeScript sobre JavaScript
Los comentarios de que Rust no sirve mucho para backends web me resultan un poco confusos.
Mi experiencia fue la opuesta, sobre todo creando servicios pequeños como beampaint.com o probablyup.net. Si construyes apps más grandes, la complejidad podría aumentar bastante, y probablemente así sea, pero para dar la confianza necesaria en una base de código grande, Rust es excelente.
Los frameworks más antiguos traen esas cosas de base, pero es menos probable que los frameworks de Rust las tengan por su edad. Imagínate que las funciones faltantes pasan de dos a seis.
Este artículo se siente casi como la historia de mi vida.
Usaba Rails y en 2013 lancé una app SaaS escrita en Go, y tuve que resolver todo por mi cuenta. Migraciones, capa de base de datos, pipeline de assets, despliegue, validación, logging, pagos, etc.; Rails simplemente te daba demasiadas cosas.
No me gusta Go, pero aquí Go no es el culpable. Usar Go para una aplicación CRUD fue una decisión terrible desde el principio.
Con Django se siente algo parecido.
Los problemas de rendimiento se debían mucho más a las consultas a la base de datos que a Django en sí.
Entre las optimizaciones fáciles que suelen olvidarse están crear índices en la base de datos, delegar más lógica a la base de datos en lugar de al código, por ejemplo usando pipelines de MongoDB o índices parciales de Postgres, activar compresión Brotli/zstd para la mayoría de las solicitudes, cachear objetos o solicitudes con una caché en memoria como Redis, y configurar encabezados adecuados de control de caché. En el frontend también hacen falta optimizaciones como usar canvas en vez de renderizar miles de objetos como nodos del DOM. Vi un problema así hace poco en una aplicación de mapas.