We often build multi-tenant applications for Software as a Service (SaaS) providers. As we know, support for multi-tenancy is imperative for a SaaS application because a single instance of the app must manage data for multiple clients. The difference between a multi-tenant and a single-tenant app chiefly concerns the database tier, or layer. Multi-tenant and single-tenant refers to how client data is stored and accessed in a database.
But this article won’t talk about how to segregate client data in a database schema (or schemas) with Ruby gems, such as Apartment and Multitenancy. Instead, we’ll explain how to work around a different challenge – retrieving specific data depending on the tenant’s domain or subdomain name. We’ll present our own solution for this task, which you can use to build your own multi-tenant SaaS application.
Identifying the Tenant in a Multi-Tenant App
We often see URLs similar to these: tenant-one.webapp.com and tenant-two.webapp.com. For example, Slack allocates the rubygarage.slack.com subdomain for our RubyGarage team, and we must enter our subdomain – rubygarage – to sign in. This approach to building a multi-tenant web application can be called partial white-labeling (i.e. we don’t get our own full domain for Slack).
We commonly retrieve a tenant’s data (an entity) from Active Record using a subdomain name in our SaaS projects.
For example, we’re working on a project called Shopperations, a SaaS application designed for shopper marketers. Shopperations allows multiple tenants (companies) to separately work on their respective marketing projects. When a Shopperations user (a company representative) wants to sign in to the Shopperations application, they have to enter their subdomain.
So what happens when we enter a subdomain name and sign in? Put simply, the application determines who we are using this subdomain name and loads our data.
The responsible code grabs this subdomain and sends a request to the database. Since database schemas are set up correctly, only our data is retrieved and we are redirected to our unique page. It’s as simple as that!
How We Identified Tenants Before
In order to send a subdomain-specific request and receive appropriate data, we’ve typically employed a Ruby gem called Houser. But several months ago we decided to switch to a custom solution for a couple of reasons.
First, Houser can look for data only using subdomain names, whereas we needed to search for data using domain names as well. Second, we wanted to be able to ignore routes when sending a URL-based request to the database, and Houser didn’t let us do this.
Before building Detectify, we had to write our own code on top of Houser to implement these two features we just mentioned. But this implementation required two requests to the database. Since we wanted to be efficient – meaning we wanted to use a single request to retrieve data – and since we needed a more flexible solution, we decided to build Detectify.
Detectify is a simple Ruby gem, similar to Houser, that helps you retrieve an Active Record entity from a database with a URL. The URL can be either a domain name or subdomain name.
It’s easy to install and set up Detectify to meet your specific needs. Below, you’ll find an explanation of what Detectify does and how exactly the gem does it. Keep in mind that Detectify is intended for use with Rack applications.
Detectify: How It Works
To use Detectify, you must first add the following line to your project’s Gemfile:
You’ll also need to install Detectify with the standard command:
Install Detectify with the help of Bundler.
Keep in mind that Detectify only supports applications built with Ruby 2.2.2 and Rails 4.2 or higher. There’s no need to install any other gems to run Detectify.
Now, let’s take a look at the short Middleware module that our developer created for Detectify.
Detectify’s workflow starts in the middleware.rb file. Our middleware receives a new Rack request and initializes the Detector (the main Detectify entity) with the URL returned by that Rack request. Next, Detectify sends a request to the next middleware, where the result of the initial request is actually used.
The detector.rb file is the second stop in Detectify’s workflow. The Detector class receives a URL and builds a request to the database using QueryBuilder.
For now, we have a single query builder for SQL-type databases. But the query builder implementation allows you to freely add query builders to Detectify for other types of databases.
Let’s also have a look at the QueryBuilder code.
The QueryBuilder module allows us to build a more advanced request to the database than what Active Record normally does. This is why we’ve added such functions as domain_clause and subdomain_clause to this module.
To finish setting up Detectify in your SaaS app, you’ll also need to specify the Active Record model that will be used for search in configs.
Detectify helps implement multi-tenancy in Software as a Service applications. Our gem is an efficient solution for retrieving tenant data with the Rack request, which is created based on the domain or subdomain name of the tenant.
Admittedly, we could simply add several lines of code to implement Detectify’s functionality in every new Software as a Service project. But we believe it’s best to use a scalable solution that can be easily modified to work with more than just SQL-type databases.
Go ahead and try out Detectify in your own projects, and extend it if necessary. And if you have any suggestions for changes, commit to Detectify on GitHub.