Rewrite it in Rails
In 2022 I started building an application for creating Customs declarations. After evaluating some options and writing code in various programming languages and frameworks such as .NET/F#, Go, Rust, React, I eventually decided to write it in Ruby on Rails. Why? Because I was already familiar with Rails and because of that I could literally go 10x faster than I was with any of the aforementioned options that were quite new to me. I told myself that this was temporary, a proof of concept and that later on, I would rewrite it in a different language that wouldn't let me shoot myself in the foot as easily as I tend to do with Ruby. The Rails application however quickly grew in functionality and my colleagues really liked the application. I could quickly build features that would make them more productive and prevent a ton of things that could go wrong (and often did initially go wrong, until I wrote some code to prevent it).
Meanwhile, I started building "version 2" of the application. I chose Rust as the language for the backend and SvelteKit for the frontend. Initially, the new version looked great, was blazing fast, but only had about 10% of the required features. As Rust doesn't really have anything comparable to Rails, I ended up having to do a lot of plumbing instead of writing business code. After a while, it became obvious that I could never write a feature complete version 2 without completely freezing version 1. So version 2 got thrown in the bin. Maybe this experience taught me something: rewriting something seems to take about as long as writing the original thing in the first place.
Was this the end of the story? Absolutely not. Having experienced the joy of Rust, with beautifully typed code and blazing fast performance, the feeling of confidence you get when something compiles without errors, I knew I had to have Rust in the application. Despite over four years with Ruby, I still regularly wrote code that had issues at runtime. Mostly issues with null values and unhandled exceptions. After each deploy, I would closely watch the error reporting tools and quickly fix any issues that would occur in production. Rust just seemed like a necessity with its lack of null and making it easy for you to handle all errors. And I always enjoy learning new things and finding better ways of writing software.
Besides Ruby on the backend, I was also getting a bit frustrated with frontend in Rails, having logic and markup spread across different files. Svelte just made that so much easier with related logic grouped in a single file. The decision was thus ti slowly write new parts of the frontend in SvelteKit and backend in Rust. Then slowly rewrite the existing Rails parts as well and eventually substitute Rails completely.
This idea looked to be off to a great start. It didn't take long to move all the "index" pages with lists of records and filters to the new stack. And it was blazing fast and (at least the Rust part) barely had any issues at runtime. Deployment got a bit messy with a lot of rules for which routes would go to Rails, Rust or SvelteKit, but hey, it would only be temporary until everything was rewritten and awesome.
However, as time went on, the Rails parts slowly got useful new features and the existing features got deeper and more refined. I performed various refactors to fix wrong assumptions I made very early on. Having a well designed database schema would benefit any version of the application, so this was good. I decided not to spend any more time updating the Rails UI as it would be replaced soon™ anyway.
This went on for months and months, and the business was successful enough that we could afford to employ a full time developer. This would be great and would surely speed up the migration to Rust domination.
It is good to dream big, I guess. And a big dream it surely was. In reality, I spent a lot of time writing abstractions and mechanisms for accessing the database, to ensure that users could only see the data they are allowed to see, keeping track of all changes, search and filters and using the type system to enforce it all. Then I realized I would also need background jobs, a pub/sub system for pushing updates to the client, e-mail capabilities, OAuth 2.0 support for connecting with external services, SMTP and POP, object storage, PDF generation and more. While there are plenty of Rust libraries available for doing all of these, selecting the right one and wiring everything together was a lot of work. We were having a lot of discussions avout technical implementation details, what ORMs for Rust might be useful, when Svelte 5 would come out and how much better it would be, and so on.
After probably a good year of writing Rust and Svelte, it dawned on me that our users didn't benefit from any of this. Sure, parts of the application were now very sleek and blazing fast, but to be honest, those parts were never an issue to begin with. The most used pages were still running Rails and they often did 10x more than the current state of the "beta version" of that same page, written in the new stack. With days and often weeks on end being spent on adding absolutely no direct value, I had to keep telling myself that it was an investment, and that it would pay off in the long run.
And then I called BS on myself. Some investments are just dumb and will not work out, no matter how hard you want things to succeed. I was getting stressed because our users needed stuff that I wasn't building for them. And I was getting more stressed just thinking about the sheer amount of stuff that still had to be (re)written in Rust. I was still the only person on the team maintaining the Rails app, the rest was only familiar with Rust and Svelte. The application became rather complicated, with many changes to be made in multiple places if you just wanted to make a small change. There was only one reasonable decision to be made.
We had to Rewrite it in Rails. Well, not the whole thing, just the parts that were now running on the "new" stack. And the rest of the team would have to start learning Rails, I guess, whether they would like it or not. Even though in my opinion, this was the only good decision, it was still a very hard one to make. After all, we had spent so much time and wrote so much code, only to just throw it all away? And even potentially alienate the people on the team who may not even like Ruby or Rails? Luckily, the team seemed to understand the decision and was open to learn new things. Well, I wouldn't blame them if they would end up not enjoying it at all, but hopefully they'll give it an honest try at least.
What makes Rails so good then, that it is apparently better than Rust of all things and Svelte with TypeScript, the darling of web developers everywhere? Perhaps being the foundation for several hugely successful companies, who still contribute to it as much as ever, counts for something. Rails has lots of batteries included and the ecosystem consists of many mature and stable libraries to complement it. It let me focus on building the product instead of writing all of the supporting code underneath. And due to its opinionated design and conventions, it is often very concise compared to other languages and frameworks.
Was the attempt at using different languages and frameworks a waste then? I personally don't think so. First of all, I've learned a lot. I always enjoy learning new things and learning a new programming language or framework often gives me a lot of new insights and ideas that make me a better programmer in any language (or at least I'd like to think so).
More importantly, I learned that writing a web application is all about making tradeoffs. There are a lot of decisions to be made and no choice will be perfect. You either have to accept the flaws of your chosen framework or do the investment of writing things yourself. But you have to consider whether that investment will really give you a competitive advantage and whether it will really pay off over time.
In this case, I think it was a great decision to go back to building everything in Rails. Rewriting a lot of stuff back to Rails was frankly rather trivial and it reduced a lot of stress for me. What's more, these last weeks I've felt very productive again, building new features, improving existing ones, updating the UI, fixing stupid bugs. I also really enjoy working more closely with my colleagues who use the application all day, every day and figuring out how I can make the product better for them.
But what about the downsides of Ruby and Rails then? Well, those are simply things to take into account when writing code. Let me give my opinion here. Have many issues at runtime? Test more. Does it turn into unmaintainable spaghetti code after a while? Only if you let it. It's typically caused by developers, not programming languages or frameworks. There are plenty of ways to nicely organize your code and you should probably spend more time refactoring. Rails doesn't scale? I think that if you can't scale Rails, you probably can't scale anything else either. Rails is slow? It is fast enough, and there are plenty of ways to "cheat" with Turbo and caching. Fun fact: the Rust stack wasn't noticably faster for our users. On Rails, the most heavy page has a P95 duration of 338 ms. There is of course room for improvement but it's plenty snappy.
Interestingly, all of the things that used to annoy me about Ruby and Rails now annoy me much, much, much less. I have accepted that there is no perfect language or framework. You just have to know its strengths and weaknesses and deal with them. Luckily, there are some really good frameworks out there for people who want to focus on building a good product. I think Rails is probably one of the best, but there are a lot of other long-lived frameworks with lots of active contributors that are probably just as good. It might still be worth it to choose a more exotic language, framework or to dive in and write your own. If only for the opportunity to learn these kinds of lessons on your own.