Multitenancy, Grails and me — The grand finale

I’m a Grails lover for a while and started to use grails even before the 1.0 stable version was released (in 2008), and have used heavily all these years in my freelancer projects and even on the SaaS products that my company runs (Moskit CRM and Simbo CRM). Both of them deeply depends on a Multi Tenant architecture so we can add, remove and manage clients with isolated data at runtime.

My relationship with the Multi Tenant architecture and bringing them to the Grails Ecosystem is a “Love and Hate” novel with a four chapters that I’ll briefly describe now.

#1 The Multi Tenant Core Plugin

https://github.com/multi-tenant/grails-multi-tenant-core

The grails-multi-tenant-core was the first widely known MT Grails Plugin. It was initially developed by Scott Ryan to a internal job in the company he was working that time (If I’m not wrong) in 2009/2010 (Yes! Seven or eight years ago).

This was the same time I was starting to build our first SaaS product, Simbo CRM (late 2010), so as I was talking a lot with Scott, I felt comfortable to build our product using this plugin. The plugin worked great in the beginning, but due the fact it was built for his needs, and those needs (one schema per customer approach) was very different from mine (one schema for all customers and using a column in each table to discriminate tenants) after a few time the plugin could not fit my project anymore. I even forked and started to commit some improvements for the plugin thinking on people with same needs than me.

But, after some time and right in time before the product launch, I’ve met Kim Betti in the github community because he had a problem similar to mine, and started to build another Multi Tenant Solution for grails, the Multi Tenant Single DB plugin.

#2 The Multi Tenant Single DB Plugin

https://github.com/multi-tenant/grails-multi-tenant-single-db

Kim is an excellent developer and a real nice guy, we’ve talked a lot during the time (early 2011) he was coding this plugin alternative. I’ve engaged with the plugin development and added his plugin in a pre-alpha-stage in my application (a couple weeks before launching). Everything went great, and the plugin worked great, beside the fact that it was a huge structure to achieve the goal, since there was three different plugins for it (multi-tenant-single-db the core plugin, hawk-eventing, a simple event machine for grails and the jvm, and hibernate-hijacker, a way to intercept hibernate queries to add tenant filters in that time). Of course this was done thinking that we could use them separately to get only part of those benefits if one application needs it.

As I said, the plugin worked great and it is still running on the application with more than 500 active paying companies using it.

#3 The DIY chapter

Let's handle those hibernate filters!

In 2015, we’ve decided to build and launch another product, Moskit CRM, and again we’d to think on how we would handle the MT issue, since the Multi Tenant Single DB plugin development had been discontinued after Kim moving for another job and me stop to being involved in the plugin evolution. So, me and the team (Victor Mitinho Vasconcelos and Rafael Loura Felini) had decided to write a MT solution ourselves.

At this time If I remember correctly, Hibernate already had support for native multi tenancy, but was not available in grails due version incompatibility. So we’ve did it ourselves. It was an excellent opportunity to learn on hibernate interception and filters. Of course the homemade solution always takes more time than using something ready, but gives you this opportunity to dig in the deep diabolical and dark hibernate world.

So, as the application was a SPA application with the grails part only providing a REST interface to the view layer, all requests carry an API key from the logged user and with this API key, we can determine its tenant and activate the correct filters for it.

We went even further and created a lot of other hibernate filters that in some cases enabled some other conditions (like user permission level, and logical deletion), but this will rest in another post :)

#4 The Grand Finale

A native support available — source available at https://github.com/lucastex/grails-mt

And now, while studying a little this weekend I saw that grails and hibernate now offers a MT solution out-of-the-box using the encapsulated hibernate feature. So people don’t have to suffer as we did in the past now that you can run a MT SaaS easily with this Grails support.

I’ve built an example app to understand how to configure and get this feature running in grails, it is described below :) Sources available in https://github.com/lucastex/grails-mt

Simple requisite, a domain class Show that stores some info for a rock band show. After that will populate some data for three different bands and based on the URL used to reach the application, we'll do a DNS lookup in the database to find witch tenant is the owner for that domain and look for all of its shows.

This is simple, and we could make this using a simple query where clause, the point here is to show the MT concept in action when querying all shows with a "find all shows" statement, without having to specify which band I want (the MT plugin did this).

Making the magic

1. Define your domain class that will be used across Tenants, implement the MultiTenant<T> and add the tenantId property:

class Show implements MultiTenant<Show> {

String tenantId

String city
Double price
Date when

}

2. Create your TenantResolver class that will analyze your current request context and define which tenant is the current one

class MyTenantResolver implements TenantResolver, AllTenantsResolver {

Iterable<Serializable> resolveTenantIds() {
new DetachedCriteria(Show).distinct('tenantId').list()
}

Serializable resolveTenantIdentifier() throws TenantNotFoundException {

RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes()
if(requestAttributes instanceof ServletWebRequest) {

String subdomain = ((ServletWebRequest) requestAttributes).getRequest().getRequestURL().toString()
subdomain = subdomain.substring(subdomain.indexOf("/") + 2)
subdomain = subdomain.substring(0, subdomain.indexOf("."))
return subdomain

} else {
throw new TenantNotFoundException("Tenant could not be resolved outside a web request")
}
}
}

3. Configure your TenantResolver in your application.yml configuration file

grails:
gorm:
multiTenancy:
mode:
DISCRIMINATOR
tenantResolverClass: grails.mt.MyTenantResolver

That's it, your application will be up and running in a multi tenant environment. For this example, I've made a simple controller that shows the band Agenda, and showing it in a simple page.

This is where the magic happens, this is where the Multi Tenant configuration works for us, look how the AgendaController code is clean and knows nothing on the Tenant concept that our application relies on:

package grails.mt

class AgendaController {

def index() {

def shows = Show.findAll()
[shows: shows]
}
}

That's it, the multi tenant configuration will intercept the Show.findAll() query adding the where clause that you need to handle this request. The TenantResolver will look for the domain used to reach the application, stripe some useless parts and get only the first part (after :// and before the first . ). This will define the tenantId for this request and will be used to filter database data, check it out how the same code behaves from different URLs (different tenants).

This is my database data, only one table with different shows for different bands

That's it. Keep coding :)