Building a Multitenant application with Apartment gem

Single tenancy Every customer is having their own software instance and DB and it serves for only one customer. Here, the software can be customized to meet the specific customer requirements as it is independent.

Multi-tenancy

Multitenancy implements a shared architecture where a single instance of the software application serves multiple customers. We call each customer as a tenant. Multitenancy ensures data isolation for each client. A multitenant app is easier to maintain and is economical. The core principle in multitenancy architecture approach is often misunderstood with multi-instance architectures. In multitenancy architecture, it is the single instance of the application which is being shared between multiple tenants. Hence, multi-instance architectures aren’t the same as multi-tenant architectures.

Advantages

  • Shared infrastructure lead to lower costs by sharing same power resources and software across its clients.
  • In single-tenant applications, you have to manage different sets for each client while in multitenant applications you have to monitor and provide administration for a single platform only.
  • Entire system becomes more scalable
  • Upgrading the software version and installation becomes easier for multitenant applications.
  • Easier to add new clients.
  • Configuration can be changed without touching codebase.
  • Easy maintenance

Multi-tenancy on the database layer or model layer

Multitenancy can be done on the database layer or model layer. Both solutions have pros and cons. The concept of multitenancy on database layer is creating for each user a new database or schema which is the best method to avoid data leaking to another tenant. But some applications have architecture constraints where only one database can exist in the application. The concept of multi-tenancy on the model layer is faster and easier to implement based on ActiveRecord default scopes, but also riskier. In one database, each customer is connected with his own tenanted models by the foreign key. A bug in the application can cause irreversible data leaks from one tenant to another. The two most popular Ruby Gems which allow for creating multi-tenancy in Rails are: It’s impossible to conclude a universally best solution for multi-tenancy, but Apartment gem seems most reasonable to use when we use Postgres schemas. In this blog post, we are gonna use Apartment gem to implement multitenancy in Rails application.

Using Apartment gem

Apartment gem provides tools to handle multiple tenants in our Rails application. It has a simple installation process and it basically creates tenants and to each tenant, the gem will automatically assign a new isolated schema and the creation of isolated database instances is also possible with Apartment.

Let’s create a new Rails app

rails new blog-post -d postgresql
cd blog-post
bundle exec rake db:create

Adding the Apartment gem to your Gemfile

Now open up the Gemfile in your rails app and add,

gem 'apartment'

Now generate the apartment config file,

bundle exec rails g apartment:install

This will create a config/initializers/apartment.rb file as the configuration file for us. There is a bunch of configuration stuff to use.

Create the model

For example usage, we will be creating models author and post.

bundle exec rails g model user name
bundle exec rails g model article content
Here users play the role of tenants and articles will be tenanted data. Each user must have a separate schema in Postgres database. We assume that user’s name is a valid Postgres schema name. We have to add a unique constraint to it. In the users table migration file,
class CreateUsers < ActiveRecord::Migration[5.1]
  def change
    create_table :users do |t|
      t.string :name, null: false
    end
    add_index :users, :name, unique: true
  end
end
Also, add validation in app/models/user.rb:
validates :name, presence: true, uniqueness: true
Then run migration:
bundle exec rails db:migrate
In Apartment gem, each tenant should be created and deleted when the new author is created and removed. Adding Apartment methods calls in app/models/user.rb :
class User < ApplicationRecord
  validates :name, presence: true, uniqueness: true
  after_create do |user|
    Apartment::Tenant.create(user.name)
  end
  after_destroy do |user|
    Apartment::Tenant.drop(user.name)
  end
end
This will automatically create a tenant whenever a user is created by taking the value of name field in the User model (create a Postgres schema for new the user) and remove from the database it when the user is deleted.

Specify common models

In any multitenant application, some models will be common for all the tenants and some will be tenant-specific models. In our example, Article model will belong to a tenant and the User model will be excluded from it and remain in the global (public) namespace. So we need to specify it in config/initializers/apartment.rb:
--------------------------
Apartment.configure do |config|
# Add any models that you do not want to be multi-tenanted, but remain in the global (public) namespace.
# A typical example would be a Customer or Tenant model that stores each Tenant's information.
config.excluded_models = %w{ User }
--------------------------
Now, we have the User model as the common model.

Specify tenant names

Apartment gem needs to specify tenant names and assign them accordingly to run migrations for them and create proper tables in Postgresql schemas. In our case, we have to modify only one uncommented option below the excluded models line that we have added in the previous section in config/initializers/apartment.rb to:
config.tenant_names = lambda { User.pluck(:name) }

Specify a middleware

To tell the application to work with subdomains,  ensure that the following line at the end of the apartment.rb file is left uncommented.
Rails.application.config.middleware.use Apartment::Elevators::Subdomain

See it works

Finally, see how multi-tenancy in the Apartment gem works by creating two users in Rails console.
User.create (name: 'Angel')
User.create (name: 'Sam')
This will create users and also two schemas with articles table for both of them. By default, we are in the public schema. In order to switch to a different schema we use a special Apartment method:
Apartment::Tenant.switch!('Angel')
This will set current apartment to user named Angel. Creating some posts for this user in this tenant:
Article.create(content: "Angel’s article")
Article.all
=> #<ActiveRecord::Relation [#<Article id: 1, content: "Angel’s article",
created_at: "2018-01-09 09:30:15", updated_at: "2018-01-09 09:30:15">]>
Now switch to the second user and show all articles:
Apartment::Tenant.switch!('Sam')
Article.all
=> #<ActiveRecord::Relation []>
We get an empty result, see multitenancy working:) By the way, showing all users:
User.all
=> #<ActiveRecord::Relation [#<User id: 1, name: "Angel">, #<User id: 2, name: "Sam">]>
As the User model is excluded from multi-tenancy it can be seen globally. The public schema has its own articles table, so user’s posts will not be visible in this schema. More information can be found on Github gem page, like the built-in ability of Apartment gem to switch tenants per HTTP request.

References

]]>