Maybe just use Rails
Rails gets a bad rap from “real programmers”. In the seven years that I spent as an engineer at Google, I heard constantly about how Rails was “unable to scale” and not much else.
The concern about whether Rails can scale sort of makes sense at Google scale. From the announcement of a new Google product to the announcement of its cancellation six months later, this theoretical Google product can bask in the big G’s brand halo and benefit from the hordes of bloggers eager to share about all things Google. That drives traffic to the product and it would sure be nice if it didn’t collapse under the weight of interested passerby. Maybe it’s true: Rails may not be a good fit for Google scale traffic.
Yes, Shopify and GitHub and Twitter all made Rails work at massive scales. However, each ran into their own scaling issues that required skilled engineers to solve. There’s no question that a web server running Rails won’t match the peak throughput of one running similar code written in Go or Rust.
With all that being said, I was surprised when I left Google to find that the common wisdom among Google engineers also extended to engineers at early-stage startups operating with vastly different needs. Unlike at Google, speed of implementation and the ability to focus on the needs of the business were both far more important than the ability to scale for hypergrowth at a moment’s notice.
I think “shiny new thing” syndrome is at least partially to blame: Rails is out of favor and people want to try something new.
However, I think people often start down this path without thinking through what lies ahead. With that in mind, I’ve assembled my own list of annoying questions:
The questions
- What ORM are you planning to use to interact with your database?
- What method of data exchange (e.g. REST, GraphQL) are you using to communicate between your frontend and backend?
- If you’re planning to use GraphQL, how do you plan to address the N+1 query problem?
- What library are you planning to use to translate your ORM records into your data exchange layer? How well maintained is that library?
- How are you planning to store files in production?
- How are you planning to store files locally during development?
- How are you planning to send emails in production?
- How are you planning to test email sends locally during development?
- How are you planning to run jobs that need to happen at a specific time (e.g. a monthly billing job)?
- How are you planning to run background jobs that need to happen off of the main web server thread?
- What payment processor are you planning to use?
- Do you need a library to integrate with that payment processor?
- How do you plan to mock that payment processor for local testing and continuous integration?
- How are you planning to handle authentication in production?
- If you plan to use a third-party authentication provider in production (e.g. Auth0), are you planning to use that same authentication provider locally and in CI?
- What are you planning to handle authorization (i.e. determining who can do what within your app and enforcing that)?
- How will database schema migrations happen when schema changes occur?
- How will data migrations happen when schema changes occur?
- When your new code is deployed to a new environment (staging, production), how will you run those migrations automatically?
- How do you check whether records in the database conform to business-logic constraints that can’t be modeled as database constraints?
- How do you insert seed data for testing changes locally?
- How do you write code for one-off tasks that need to be manually executed?
- How do you validate new records being saved to your database?
- How the heck are you planning to test all of this?
- What hosting providers are you planning on using?
I don’t know about you, but I don’t want to think about most of that stuff. However, all of those questions merit technologically sound answers and I greatly appreciate that someone else has devoted years to answering them. These are not the exciting parts to me about a startup.
When you choose Rails as the backbone of your stack, you’re joining a community that has good answers to 80% of these problems. Stuff pretty much just works. When something does go wrong - and it will - you have the enormous benefit that someone has probably already succinctly described and solved your problem on the internet, only a Google search away.
In my experience, when you assemble your own tech stack – including at Google scale – the vast majority of your time gets sucked up by this 80% that you barely even have to think about with Rails. Despite this, the developer ergonomics of this self-assembled stack rarely match those offered by Rails. At least Google has the ability to throw teamfulls of handsomely paid engineers at the problem. You don’t.
Furthermore, instead of keeping a single dependency up to date, you’re committing to keep 15 different dependencies up to date and hope that the maintainer of each cares enough about your particular stack to bother testing on it. If they didn’t and something goes wrong, you’re in no man’s land. It can be frustratingly difficult to tell which piece isn’t playing along well with which other piece and “this other person has experienced this before but they’re using foo and we’re using bar, and does that matter?”
Look - I get the appeal of assembling your own stack. I’m not some Ruby zealot: I sort of hate it as a language. I think it’s bizarre and unintuitive and easy to make mistakes in. I wish it were statically typed and I wish I were able to use the same language on both the backend and frontend. But striking out on my own feels like trying to build a lawnmower from Legos instead of buying a Honda: possible, but not recommended.
So if you feel yourself drawn by the siren’s call of assembling your own stack, reread that list above and be honest with yourself about whether you can assemble the pieces together into something that’s as coherent as Rails is off the shelf. Maybe you should just use Rails.