DEV Community

J2RGEZ
J2RGEZ

Posted on • Edited on

Booster framework vs Ruby on Rails

After all these years, Ruby on Rails is still one of the frameworks we like most. Now that we’ve developed the Booster framework, we’re curious about how it stacks up to Rails in various aspects:

  • Code: How much code do you need to write to get similar features?
  • Infrastructure and configuration: What resources are needed? How are they configured? How are they kept up and running in a production environment?
  • Scalability and costs estimation: What if our application starts to grow? How much effort and cost will there be?

The plan for this comparison is to build a real-time chat application on both frameworks with three models: Chat Room, Messages, and Users. To save time, we’ve gone ahead and built the app with both frameworks. If you want to see the full code, go ahead and check our Ruby on Rails and Booster repos.

Code

To build the Ruby on Rails application, we chose Action Cable and Rails 6. If you’ve worked with Ruby on Rails before, you know the steps we needed to take:

  1. Set up Docker using Redis as our Action Cable adapter.
  2. Configure authentication.
  3. Generate the models, controllers, and views for our app.

Maybe the hardest part was properly configuring the Action Cable client-server interaction. It’s challenging but can be accomplished by reading the docs carefully.

And what about Booster? Before we begin talking about the implementation, we encourage you to check out the Booster architecture documentation.

To begin with this app, we replicated the models from our Ruby on Rails application shared above, with the objective of creating at least one command, event, entity and read-model for each one. We didn’t need to install additional dependencies, and even authorization and authentication is built-in.

Although the objective of this article is not to do a tutorial, we want to show you just one use case:

When I create a chat room, the current user must exist in the system

@Command({ authorize: [User, Admin] }) 
export class CreateChatRoom { 
  public constructor( 
    readonly name: string, 
    readonly description: string, 
    readonly isPrivate: boolean) {} 

  public async handle(register: Register): Promise<void> { 
    if (register.currentUser) { 
      const userProfile = await Booster.fetchEntitySnapshot(
        UserProfile, 
        register.currentUser.email
      ) 
      if (userProfile) { 
        register.events(new ChatRoomCreated(
          UUID.generate(), 
          this.name, 
          this.description, 
          userProfile, 
          this.isPrivate
        )) 
      } else { 
        throw 'Error trying to create a chat room. User not found.' 
      } 
    }          
  }
}

As you can see, by using the Register object, you can retrieve information from the current user. It can also be used to read entities from event-handlers, so you will always have access to existing data in the system by just writing one line of code.

Infrastructure and configuration

Since the code is ready, it’s time to think about where we want to deploy these apps. For the Ruby on Rails application, we just went for the easiest option, which is Heroku.

Still, we’re missing a couple of important components for this infrastructure to be complete: Redis and PostgreSQL. In a Ruby on Rails application, we should configure the environment files (and the action cable one) to point to these services.

The Booster Framework has an AWS-based infrastructure (for now), which depends on DynamoDB, AWS Lambda, API Gateway, and AWS Cognito. Here’s a diagram just in case you’re curious about the infrastructure:

Alt Text

To deploy a Booster application, you just have to type boost deploy -e <environment> on your CLI, and Booster will automatically generate and connect all AWS services mentioned above for that specific environment.

Scalability and costs estimation

Another important point of comparison is cost and scalability. Let’s estimate the relative costs of a production environment for both frameworks:

Heroku production environment:

This makes a total of $650 per month, and we didn’t take into account the scalability of the system. As your application starts growing, you will need to increase not just the number of dynos, but to upgrade the PostgreSQL and Redis add-ons.

In the Booster Framework, all deployed services are included in the AWS free tier. After this initial year and depending on your region, you will mostly pay for the resources that you actively use.

So, how much would it cost to create a production environment using Booster? (eu-west-1 region)

  • DynamoDB: 64GB of capacity, 1 million read and writes on-demand = $220.89
  • AWS Lambda: 1 million requests, 1500ms request duration, 1GB memory allocated = $25.20
  • API Gateway:
    • REST API: 1 million requests = $3.50
    • Websocket API: 1 million requests = $1.14. We will also pay $0.285 per million connection minutes.
  • AWS Cognito: The first 50,000 monthly active users are free even without the free tier.

These prices would end at a total cost of approximately $249.59 per month, with a maximum of 50,000 active users. Remember that this price may vary depending on your region and your monthly requests and/or data stored.

And what about scalability? The AWS services that Booster uses will scale automatically as your applications start receiving more requests, so you don’t have to worry about any additional configuration!

Conclusions

Developing with Ruby on Rails was very comfortable but the Action Cable part was not as natural and simple as we’d like it to be. Booster, on the other hand, not only has authorization built-in, but we don’t have to worry about dependencies and the code is much easier to read. It felt like we were focusing on business actions and not low-level technical configuration and boilerplate code.

Regarding infrastructure and configuration, Booster takes the lead here. Being able to deploy your entire application by running one single command without any other additional configuration is definitely a huge advantage. In the case of Ruby on Rails, configuring it on Heroku is not a very big deal but still requires you to create the app and connect Redis and PostgreSQL for every environment.

Finally, the cost of running a production environment with Booster is much cheaper than with Ruby on Rails. Furthermore, when scaling your application in Booster you don’t need to worry about any additional or special configurations. Everything is done automatically for you.

Overall based on these criteria, we think that Booster is a better choice. Still, we would be missing the benchmarking side of both apps but that’s for another article so, stay tuned!

Top comments (5)

Collapse
 
drbragg profile image
Drew Bragg

I took a look at the code in both repos and I gotta say that the code in the RoR repo looks way easier to read, understand, and work with than the code in the Booster repo (disclaimer I work with Rails and JS on the daily but have had only basic experiences with TypeScript). I do like the directory structure in Booster but the actual code in the files is just, well, TypeScript. Just take a look at the Message models:

# /app/models/message.rb

class Message < ApplicationRecord
  belongs_to :user
  belongs_to :chat_room
  validates :body, presence: true, length: {minimum: 2, maximum: 1000}
  after_create_commit { MessageBroadcastJob.perform_later(self) }

  def timestamp
    created_at.strftime('%H:%M:%S %d %B %Y')
  end
end
// src/read-models/MessageReadModel.ts

import { ReadModel } from '@boostercloud/framework-core'
import { UUID } from '@boostercloud/framework-types'
import { Projects } from '@boostercloud/framework-core'
import { User, Admin } from '../roles'
import { UserProfile } from '../entities/UserProfile'
import { Message, MessageType } from '../entities/Message'

@ReadModel({
  authorize: [User, Admin],
})
export class MessageReadModel {
  public constructor(
    public id: UUID,
    readonly chatRoomId: UUID,
    readonly body: string,
    readonly messageType: MessageType,
    readonly createdAt: string,
    readonly createdBy: UserProfile
  ) {}

  @Projects(Message, 'id')
  public static onMessageCreated(message: Message): MessageReadModel {
    return new MessageReadModel(
      message.id,
      message.chatRoomId,
      message.body,
      message.messageType,
      message.createdAt,
      message.createdBy
    )
  }
}

Honestly, I don't even think you need to know much about Ruby or Rails to be able to read the RoR version. Not to mention you have to jump to src/events/chatRoom/MessageCreated.ts to see that something happens after the message is created. I actually took me a few minutes to figure out where this was because there's an events and an events-handlers directory. I was then looking for an events/message directory but it was in the events/chatRoom dir.

Additionally, I don't think that having authentication and authorization built in is necessarily an advantage. There are great gems that integrate seamlessly with Rails to handle those and not having them baked in means I can choose the one that fits my needs or even roll my own. When it's baked in you take away a lot of choice and restrict people from using a better library, unless you feel like monkey patching (not that that's a bad thing).

As for cost, it kinda feels like you're deliberately jacking up the price of the RoR version. Maybe you're not, Heroku is fairly popular and well known, but there are better, more cost effective options. For example, Hatchbox offers similar (if not better) features at a fraction of the cost (Your $650/mo. Heroku estimate turns into ~$150/mo.). Heck if you really want to use AWS you could do a similar RoR deploy on AWS for ~$300.

This isn't me bashing Booster. After reading through your docs it sounds like you have a pretty cool framework. Definitely seems like a solid choice for building a communication app. However, I think building an example app that plays directly to your frameworks strengths and then comparing it to a framework that isn't purpose built for that kind of app is a little unfair, hence my reply.

Collapse
 
javier_toledo profile image
Javier Toledo • Edited

Hi @drbragg , first of all, thanks for your constructive criticism and for taking the time to look into Booster; I think you hit the nail in many interesting topics where we can improve.

I used to write a lot of Rails code before switching to other things, and for me, and as it happens to you, the Rails version looks natural and 100% understandable, but after mentoring a couple of developers in their journey to Rails, I can tell you that it's easy for experts to forget the amount of knowledge packed in little things like a belongs_to or an after_create_commit.

As a major contributor of Booster, I would be extremely biased to say that the TypeScript version is "simpler", but what I can say is that the framework is still young and that the goal is to make it simpler over time. For instance, talking about the code sample you pasted, one thing we've thought is emulating Ruby's autoload to remove all these imports from the user's code. There are also plans to make projections implicit by default, as they're often just copying the data from entities. With that change, the Booster ReadModels become almost only data structures.

One thing that can surprise you at the beginning is that Booster uses a totally different architecture than Rails, as it's based on CQRS and Event-Sourcing instead of MVP, so you have to think differently about your code. In Rails, you can easily follow everything that happens when you hit a controller by just reading the code in the corresponding method. In Booster, a command only stores an event, and you can't tell what happens after the event is stored by just looking at the code. The way to do that is searching for the registered event type to see what event handlers and entities are observing it. I think that it could make a lot of sense to develop some IDE integrations that help to visualize this.

Regarding costs, the main difference between Booster and Rails is in the delivery mechanism and its price model more than the specific price or service you choose to use. I think that @j2rguez was just sharing typical configurations here, but you can always tweak your config to build much more cost-effective solutions. Booster currently works on top of AWS Lambda and DynamoDB, which have very different price models than VPS or database instances, but in the future we're planning to support deploying Booster applications on Kubernetes clusters too, where the price model is much closer to Rails'. Indeed, the opposite can also be done, you could deploy a Rails app in AWS lambda to get the same pricing benefits using Jets Afterburner, but that's probably only for the experts and the bravest :-D

Collapse
 
drbragg profile image
Drew Bragg

Booster is definitely a very different architecture than Rails, which is why I felt compelled to respond when Rails was used as a comparison framework. It's like comparing how effective a screwdriver is verses a hammer for putting a screw in a piece of wood. Booster is built to easily build this example where Rails is not. I would love to see it stacked against other node based frameworks or against something else in the serverless world like Go.

Booster is an extremely cool project and I'll definitely being following its development.

Thread Thread
 
javier_toledo profile image
Javier Toledo

Thanks for your kind words, we're always looking to keep improving Booster, and conversations like this are very useful for us! If you try it, feel free to send some issues with questions, suggestions, or feature requests!

@j2rguez is a great Rails engineer that we've been trying to "evangelize" into Booster for a while, and I can't hide that seeing him writing this article and defending Booster over his favorite framework makes me happy, but yes! Booster and Rails are very different projects that are hard to compare. Indeed a purely CRUD application is likely to look way simpler in Rails, but this is still a good exercise to understand how Booster compares to existing frameworks.

As a new framework that's trying to find its place, there's a recurrent question from devs that look at it for the first time: how does it compare to my current framework, and why should I consider switching or doing my next project in Booster?

Seeing the same application implemented in both frameworks is a good starting point to answer that question, but this example only explores one subset of everything that Rails can do, and probably not the best one. It would be interesting to see an article written with the opposite approach, starting with an app that fits well in Rails to see how Booster copes with it, I bet we'd find a lot of interesting ideas for future releases!

Collapse
 
drbragg profile image
Drew Bragg

One last thing, you may want to consider using the tag #showdev and/or swapping out the #rails tag for the #typescript or #node tags since they are a little more relevant to what you're promoting and will get you better exposure to the right people (I only stumbled upon this post because I was scrolling through the #rails tag feed)