Why is Ruby our main backend language?

Vinted was founded during the peak of Ruby popularity (2008). Choosing Ruby was as common and fashionable as Rust, NodeJS, or GoLang would be today. Many years have passed and we still stick with Ruby among other big companies like Airbnb, Shopify, GitHub, Instacart, Stripe, Twitch, Delivery Hero, StackShare, Accenture, Groupon. And here is why…

Ruby is pretty

Ruby stands out as the prettiest and cleanest language. I don’t know a nicer language. For e.g. this piece of code returns all records from the database having the “active” field value as “true” in a JSON format. And all of that in one short line!

class V1::PointsController < ApplicationController
  def index
    render json: Point.active
  end
end

As this example showcases, Ruby is very concise. It doesn’t need a semicolon or anything of that sort at the end of every line. It doesn’t require a “return” statement, unless you want to return early from a method. By default, whatever value has been calculated in the last line will be used as a return value. This is clearly more useful than the default behavior of older languages, which would end up in null being returned if you forget an explicit return statement. Braces are optional too in most of the cases. All these tiny improvements make it look nice and feel more like a human language.

Ruby is full of tiny neat features, like the “unless” keyword, which can be used as an alternative to the “if/else” statement and makes code easier to read. On top of that Rails framework adds its own set of details, like enabling addressing an array item by its index name. E.g. “arrayVariable.fifth” will give the fifth element of an array.

Testing in Ruby

Let’s have a look at a minimal test for the same controller class showcased above. It obviously is very minimal, but it tests everything starting from the controller and even including the database. Obviously you can choose to mock the database, but RSpec (a common testing framework in Ruby) gives all the tooling to test everything end to end. It creates a test database on the fly and you don’t need to worry about messing your development database with testing data.

RSpec.describe V1::PointsController, type: :controller do
  describe '#index' do
    subject { get :index }

    it 'returns PUDO points list' do
      expect(response.status).to eq(200)
    end
  end
end

Given Ruby’s weak type system, mocking is very easy too. You can mock anything without much typing. Even database fixtures are made easy compared to what other languages provide. For e.g. this line creates a record in the test database on runtime with default values, except for the “street” field.

let(:address) { build(:address, street: 'Švitrigailos g. 13') }

Performance

Ruby is one of the less performant languages in the competition and often being criticized for that. Nevertheless, we don’t face many issues with that. Our busiest Ruby service processes 50k requests per second on average without any glitches. In addition to that, we are migrating to Ruby 3 which is significantly faster. It’s unlikely to see Ruby outperform the performance oriented languages, but this is a trade off all convenience oriented languages make.

Ruby 3 performance

Figure 1www.ruby-lang.org/en/news/2020/12/25/ruby-3-0-0-released/

How do we use Ruby

Ruby often comes hand in hand with Rails. We are not an exception in this case, and we do use Active Record which comes with Rails. However, the frontend is completely dominated by React. So we are not using (at least for the new code) HTML rendering features of Rails. Instead we focus on exposing proper APIs for other services and frontend applications to consume from.

We heavily use Interactors. They make objects feel like functions and bring the best of both worlds. We can write classes as usual, with internal variables, memoization, etc. However from the usage perspective, each class feels like a function. Interactors take care of construction of an object which makes them act like functions. Please have a look at an example of an interactor usage. Let’s say we have a class “RemoteService::CreateShipment”. If this class implements the interactor pattern, it can be invoked by the following code.

RemoteService::CreateShipment.for(name, address)

One may say that it’s the same as a static method, but it’s not true. If it was a static method, multiple usages of the same method would share the data stored in the static scope of the class. In this case every invocation has a clean state. Shared state is a common source of bugs. Preventing the common state helps us avoid issues in production.

Obviously, all the common best practices of the Ruby community are practiced at Vinted. We have small classes, small methods, strict linting, high test coverage, and short variable/class names. This is one of the little-known strengths of Ruby. Ruby engineers around the world are much more aligned on the best practices compared to other languages. As a result here at Vinted, we have a few arguments on how to organize our code. Most of us love it, love its clarity and similarity to the natural language.

We do use other languages

Ruby 3 performance

Figure 2

Ruby is our default choice for common backend services. Where we value the comfort of developers. When we face specific problems, we use specific tools. For example, when we need maximum performance, we go for GoLang or Rust. When we work on AI, we choose Python. Our data analysts mainly use Scala. And obviously, some specific applications, like Android, iOS, and WEB obviously bring their own languages.

We already have a solid number of technologies and languages. Ruby is not the only one. However, our biggest headcount is Ruby developers and Ruby accounts for the greatest amount of open positions.

What if a newcomer doesn’t know Ruby?

If you want to apply for a backend engineer position at Vinted it doesn’t matter if you have any experience in Ruby. We hire indiscriminately of your previous backend language. Our evaluation process doesn’t penalize you for not knowing Ruby. You can apply for the most senior backend position at Vinted without having written a single line in Ruby language. Once you join, you will get enough time to get familiar and start coding in Ruby. Most newcomers start writing code in two weeks and get fluent in a few months.

The future

Our next step is to transition from Ruby 2 to Ruby 3. That brings a clear benefit. In terms of the longer future, Ruby is likely to stay with us. Its community is standing strong and new releases are coming in at a steady pace. Our backend infrastructure has a significant amount of tooling. Each new microservice running on Ruby instantly gets access to all the tooling we have built in these years. As long as we keep the dominance of one language, we can easily relocate teams to different parts of our application where innovation is needed the most. The more languages we have, the harder it will be to react to the changing needs of the product part of our organization. Obviously, more or less any language would work for us. Choosing Ruby is mainly a matter of taste, tradition, and our history.