Hanami vs Ruby on Rails

Oct 30, 2020

Ruby on Rails (aka RoR) is arguably the most famous web framework in the world. This statement is true in the Ruby ecosystem, of course, but RoR is famous too in other programming languages, having inspired their corresponding web frameworks.

However, RoR is not the only web framework in the Ruby world. Another famous one is Sinatra and then, perhaps less well known, we have Hanami.

Hanami was inspired by the ideas presented in the Domain Driven Design (DDD) work by Eric Evans. At Lavanda, we use it and we like it very much.

At Lavanda, we use both Ruby on Rails and Hanami. Our experience with both inspired the content of this blog post, in which we'll be comparing the two frameworks putting extra emphasis on the way defaults of each framework guide the engineer to develop a web application. We hope that this blog post will make you want to download, install and play with Hanami - and perhaps even become productive with it.


Side Note: What does Hanami mean?

ā€œHanamiā€ is the Japanese word for flower viewing - the traditional enjoyment of the transient beauty of flowers. You can find more information about it in Wikipedia.


Default Project Folder Structure

Let's start with a comparison of the default folder structure of a project that is bootstrapped with either of the frameworks.

The main differences are the following:

app vs apps

Ruby on Rails creates a project with a folder tree app that hosts the application specific files. On the other hand, Hanami creates a project with a folder tree apps. Inside apps, you can host one or more applications. Hence, you can have a sub-folder named, for example, web, that holds your web application and another one called api, that holds your exposed JSON API.

In other words, in Hanami, you can host multiple applications within the same project. They will all share the same domain model.

lib Tree

Both frameworks create a lib folder, but their purpose and importance is completely different.

In RoR, we use the lib folder to write some utility, accompanying classes that have horizontal functionality. Some times, the code we write in lib could be potentially shared with other Ruby or RoR projects, if it were extracted into a gem. It is code that is application independent, but it does not actually model the domain of the project. We have seen RoR projects that they have very few lines of code inside the lib tree, or even none. Hence, this folder does not usually play very important role in the RoR projects.

On the other hand, in Hanami, the lib folder is the heart of the project because it is used to model the whole business domain. We put a lot of code inside this folder. Everything is application independent and ready to be shared by all applications that are hosted inside the apps folder. Some of the things that we put inside the lib folder are:

  1. entities

  2. repositories

  3. interactors

  4. validators

Keep on reading to learn more about these.

Hence, in Hanami the lib folder has the most important part of the code of the project. The apps holds code that relies on the lib folder code. lib folder code stands on its own and completely models the application-independent aspect of the world.

config Folders

Both projects have a project-level config folder. This includes files and folders necessary to configure the whole project. In addition to this Hanami has also one config folder per application. In other words, one can configure global project settings as well as specific configuration per hosted application.

db Folders

This folder serves the same purpose in both frameworks. This is where we keep database specific configuration, including the schema migrations.

Test Tree

In RoR the test tree is holding the whole automated test suite. In Hanami, it is the spec folder that does that. This is because RoR integrates, by default, with the mini-test, whereas Hanami integrates with RSpec.

MVC

Both frameworks are MVC (Model View Controller) web frameworks. However, they apply this software design pattern in a slightly different way.

Let's see how.

Controllers

In RoR we have one controller class implementing multiple actions.

On the other hand, in Hanami, we have one class per action. That's why we also call them controller action classes.

Generally, something that we really like with Hanami, is that it favours small classes, that have a very simple public interface and they do a single thing. And one occurrence of this idea is how Hanami asks you to put your controller actions into separate classes. Smaller, simpler classes have many benefits, amongst of which are the ability to easily test it, maintain it, read it and understand what it does, reuse it and refactor it without generating ripple effects. This all falls into the general software engineering principle of Single Responsibility.

Otherwise, controllers between the two frameworks have a lot of similarities. For example, you can install before action hooks, i.e. code that is executed before the main code of the action.

Views vs Templates

In RoR we have the views being *.html.erb files. They contain the HTML content to be returned. They can include Ruby snippets that build the dynamic part of the page/resource requested. We have one such file per action. They can be combined with special *.html.erb files that are called layouts.

On the Hanami side, we have exactly the same thing as we do in RoR, but these files are called templates and not views. And, on top of that, there is one more layer which is called views. The views in Hanami are Ruby classes that prepare the data for the templates. In other words, in Hanami we are advised to use views to prepare the data for the templates, and not the controllers. Whereas, in RoR, we prepare the data for the views inside the controller actions.

Again:

  • RoR: only views and data for the views prepared in controllers.

  • Hanami: templates and views, with data for the templates prepared in views and not in controllers.

In the following picture you can also see the difference in the folder structure.


  • In RoR, the views folder is inside the app folder. There, you can find a sub-folder for layouts too.

  • In Hanami, we have templates and views inside the application specific folder tree. Note that Hanami can have layouts too, but they are just other templates files.

Models vs Entities

In RoR all models derive from ActiveRecord::Base. Moreover, they are feature and functional-rich class. Some they say that they are quite heavy because they inherit so much code from the base class.

Hanami does not have models. It has, what they call, Entities. Each entity derives from the class Hanami::Entity. Entities are light-weight classes with very minimum functionality. They have an id and you can read the values of its attributes, without being able to change/update them.

Both types of instances, either models in RoR and entities in Hanami, represent one row (at least usually) from a table. However, in RoR, you use the same instance to both read and update the corresponding row. In Hanami, the instance db-related state is read-only. If you want to change the persistent state of an entity, you have to use a Repository specially designed for this. Keep on reading in order to understand what a Repository is.

Models and Entities differ also on the folder they live in.


In RoR the folder is called models and it is inside the app tree.

In Hanami the folder is called entities and it is inside the lib tree. It is application independent and that is very useful, because the entities can be shared amongst multiple applications in your Hanami project.


ActiveRecord vs Repository Pattern

Having given some details about the differences between models and entities, we would like to emphasize here the fundamental difference between the two frameworks with regards to the way they map objects to relations.

Models in RoR are an instantiation of the ActiveRecord pattern, whereas Entities, with the help of Repositories they instantiate the Repository Pattern.

Here are the definitions of the two patterns as given by Martin Fowler.

ActiveRecord Pattern

An object that wraps a row in a database table or view, encapsulates the database access, and adds domain logic on that data.

Repository Pattern

A Repository mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects.

This makes the two frameworks, RoR and Hanami, differ quite a lot when it comes to interacting with the database. For example, in Hanami, there is no code like this:

book = Book.find(1)
book.title = 'Of Mice and Men'
book.save


In order to update the title of a book, you should write something like this:

book_repository = BookRepository.new
book = book_repository.find(1)
data = { title: 'Of Mice and Men' }
book_repository.update(book.id, data)


The philosophy of Hanami is to try to prevent developers from spreading query-related chains of statements all over the code base.

This is something that Hanami doesn't like:

Book.where(author_id: 23).order(:published_at).limit(8)

As you can imply from the above Ruby statement, the technique to access the persistent state is not exposed outside of the Repository. All the application code is using, it is the business-friendly methods that Repository is exposing. And that's good! Because whenever you want to change the data access layer implementation details, then you don't affect the rest of the application code.

Repositories Folder

All the repositories in Hanami are under the lib tree inside the repositories folder. Here is an example picture of such a folder:




Repository Methods

As we said above, in the Hanami repository, we expose business-friendly methods and we hide the technique we use to retrieve data from or update data to the database. Here is the public API of an example repository:


Validations

Let's talk now about another difference between the two frameworks. That of the validations.

RoR Validations

In RoR, validations are written at the model level. Like this:

class Article < ActiveRecord::Base
  validates :title,
            presence: true,
            uniqueness: { case_sensitive: false },
            length: { maximum: 25 } 
end

We do like Rails validations because they are easy to write and easy to read. What we don't really like very much is that they are written at the model level. We believe that the validations should be part of a form model, rather than of an ActiveRecord model, because validations, usually, change, depending on the use case.

Hanami Validations

In Hanami, validations are not written at the entity level. They are separate classes, validator classes, and they can be used anywhere in the code they are needed.

class CreateArticle
  include Hanami::Validations
  
  predicate :unique_title?, message: 'has already been taken' do |title|
    ArticleRepository.new.find_by_title(title).nil?
  end
  
  validations do
    required(:title) { str? & size?(1..255) & unique_title? }
  end
end


Hanami Validator Classes

  • They include the mixin Hanami::Validations

  • They have a block validations do ... end which includes all the validations.

  • The validations are written using a special language. This language has constructs that are called predicates. One can invoke a predicate or more, combining them with the & operator.

  • The validator class can define its own custom predicates, using the block predicate <name of predicate>... do ... end. The custom predicates can be used alongside the default Hanami predicates when defining the validation rules inside the validations do ... end block.

Hanami Custom Global Predicates

Besides the custom predicates that one can define inside a class validator, with the help of the method predicate (and which are called custom inline predicates), one can define reusable predicates at a global level. I.e. custom global predicates that can be invoked in any class validator validations block.

These custom global validation predicates need to be defined in a module. Here is an example:

module AwesomeHolidays
  module Predicates
    include Hanami::Validations::Predicates
   
    REGEXES = {
       phone_number: /\A\+?\d+[\-.\s\d]+\d\z/,
       email: /.*@.*\..*/,
       url: /\Ahttps?:\/\/.+\z/
    }.freeze

    self.messages_path = 'config/predicate_error_messages.yml'

    predicate :phone_number? do |string|
      string =~ REGEXES[:phone_number]
    end

    predicate :email? do |string|
      string.nil? || string.empty? || string =~ REGEXES[:email]
    end

    predicate :url? do |string|
      string =~ REGEXES[:url]
    end
  end
end

In the example above:

  • Custom predicates are defined inside the module AwesomeHolidays::Predicates.

  • This module should include the Hanami::Validations::Predicates mixin. This gives the predicate method to the module.

  • The predicate method is used to define the custom predicates.

  • Note that the predicates do not declare the error messages inline. they use the "config/predicate_error_messages.yml" file.

Then, all you have to do is to incorporate this module into your class validator. If you do that, then you have its predicates available to use in the validations do ... end block. Something like this:

class SessionValidator
  include Hanami::Validations

  predicates AwesomeHolidays::Predicates

  validations do
    # ...
    required(:email) { email? }
    # ...
  end
end

Note that when incorporating the global custom predicates module into your class validator, you use the method predicates and not the method predicate

Use a Hanami Class Validator

Defining a class validator is useless unless you invoke its services in other places in your code, where you need a validation to take place.

Here is an example of using a validator inside a controller action:

module Web
  module Controllers
    module Articles
      class Create
        include Web::Action
        
        # We incorporate the validator class and we attach it to the +params+.
        params CreateArticle 
     
        before :validate_params
    
        def call(params)
          # work here with valid params
        end
  
        private

        def validate_params
          return if params.valid? # +params+ now responds to +valid?+ using our validator class +CreateArticle+

          halt_with_error(422, ...)
        end
      end
    end
  end
end

The validator class is the CreateArticle and we attach it to the params. Then params responds to #valid? using the validation rules defined in the CreateArticle class.

Associations

Associations and the way they are defined, is another big difference between the two frameworks.

Rails Associations

Rails allows you to define associations at the model level. It is part of the ActiveRecord::Base API. Here is an example:

class Article < ActiveRecord::Base
  belongs_to :author

  # ...
end

class Author < ActiveRecod::Base
  has_many :articles

  # ...
end


Hanami Associations

On the other hand, Hanami defines associations at the repository level. They need to be defined only if you really need them. For example, when you want to load associated models.

Here is an example:

class AuthorRepository < Hanami::Repository
  associations do
    has_many :books
  end

  def create_with_books(data)
    assoc(:books).create(data)
  end

  def find_with_books(id)
    aggregate(:books).where(id: id).as(Author).one
  end
end

In the above example code, we declare the association of an Author to their books inside the AuthorRepository. We do it using the associations do ... end block. Then, we can load, for example, the author alongside their books. This is what we do inside the find_with_books method.

Undoubtedly, it requires more code to navigate associations in Hanami, if compared to Rails. But, yet, this is due to the Hanami philosophy and the repository pattern it is promoting.

Hanami Interactors

You may have already heard about the Service Objects, plain old ruby objects that have a single responsibility and that they are easily testable.

Hanami likes this idea very much and for that reason, it offers the Interactor tool. A Hanami Interactor is an object that works like a Service Object.

  • It can be initialized and then

  • It has only one public method named call

One more thing: they integrate well with Hanami validations. We will explain this with the following example:

module Interactors
  module Sites
    class Update
      include Hanami::Interactor
 
      def call
        # do the work inside +call+ assuming 
        # interactor has been initialized with valid params
        # ... 
      end
   
      private

      def valid?
        # this method is called automatically when you call +call+
        # If it returns +false+, then +call+ will not fire
      end
    end
  end
end

In the example above, you can see the public call implementation. Also, you can see a private method named valid?. The valid? is automatically called when you call the call method on an interactor. And if it returns false, then call is not invoked.

Usually, the valid? invokes the services of a Hanami validator object.

Hanami @ Lavanda

As we said at the beginning, @ Lavanda, we have already started using Hanami in production. Here is our preferred layered architecture:


  1. At the top we have the controllers. These are responsible for handling the request and returning a response back. They have WEB/HTTP logic only.

  2. Controllers rely on Interactors to carry out the real work that the business requirement dictates.

  3. Interactors rely on validations, repositories and possibly on other interactors.

Closing Note

In this post, we did a quick comparison between Hanami and Rails. Certainly, non-exhaustive.

@ Lavanda, while working with Hanami, we found a lot of practices making great sense. Such as:

  • Model of the domain in an application-independent folder, reusable by multiple applications.

  • Repository pattern that protects flooding of the application code with query data-access API commands.

  • Small classes that do a single thing.

  • Service objects with a very clear simple interface.

  • Light-weight model classes with associations and validations defined externally, in other classes, Repositories and Validators.

These practices are framework-independent and can be applied even to a Rails project. And that's the good thing about them. If you really like Rails for many other reasons, you may well apply these practices to your next Rails project and avoid using the corresponding Rails defaults.