Grails Plugin - Multi Tenant

From Blue-IT.org Wiki

An introduction on the concept of multitenant can be found at:

Documentation

1. Declare mode in Config.groovy

// Multi Tenant
tenant {
	// multiTenant is the default 
	// do "grails create-dns-map" when changing tenantDomains
	// this stroes the dns/tenant mappings into the database
	resolver.request.dns.type = "db"
}

Later on you can use tenant[dot] to refer to the TenantMap domain class (see later on).

You also have to set a per-environment serverUrl stem for creating absolute links depending on your hostname:

environments {
[...]
     development {
         grails.serverURL = "http://localhost:8080/${appName}"
         grails.tenant.domain = "localhost"

2. Declare Tenant Class

This class - I use Organisation.groovy for example - should be a top level class.

E.g. "Organisation" hasMany "Users".

import grails.plugin.multitenant.core.groovy.compiler.MultiTenant;
@MultiTenant
class Organisation {

[...]

3. Create a TenantMap domain class

This is achieved by using the command

grails create-dns-map

This automatically creates a domain class with the name DomainTenantMap.groovy.

package tenant

/**
 * Maps domain name to tenantId
 */
class DomainTenantMap { 
	
  String domainName
  Integer mappedTenantId
  String name
 
  static constraints = {}
}

This class maps to a database table domain_tenant_map.

4. Build a MultiTenant environment in BootStrap.groovy

Tenants (e.g. organisations) are created by instantiating a new tenantDomain ConfigurationHolder.

import grails.plugin.multitenant.core.util.TenantUtils
import org.codehaus.groovy.grails.commons.ConfigurationHolder
import tenant.DomainTenantMap // was created with grails create-domain-map

class BootStrap {

   def init = { servletContext ->	
 
       // This refers to Config.groovy: grails.tenantDomain  = 'myweb1'
       def domainName = ConfigurationHolder.config.grails.tenantDomain

	def myweb1Tenant = DomainTenantMap.findByName('myweb1_dev')
		
	if ( myweb1Tenant ) {}
	else {
	   myweb1Tenant = new DomainTenantMap(
				  name:'myweb1_dev',
				  domainName:domainName,
				  mappedTenantId:1)

	   if (myweb1Tenant.save(flush:true) ) {
		   	println "TenantMap entry for myweb1_dev created."
	   } else {
	   		println "Error creating TenantMap entry for myweb1_dev."
                       website1Tenant.errors.allErrors.each { println it.defaultMessage }
	   }
	}

The property domainName refers to the correspondent field in the database domain_name. The property mappedtenantId to the field name.

mysql> select * from domain_tenant_map;
+----+---------+-----------------+------------------+----------------+
| id | version | domain_name     | mapped_tenant_id | name           |
+----+---------+-----------------+------------------+----------------+
|  1 |       0 | myweb1          |                1 | myweb1_dev     |
[...]

You can use sub.domain.tld by concatenating the strings when setting domainName.

Now you can create data for your tenant:

TenantUtils.doWithTenant (website1Tenant.mappedTenantId) {	
			
    def organisation1 = new Organisation (name: 'Organisation 1')

    [...] // create Users and other stuff

5. Include a login from spring security plugin

OrganisationController

The save()-method of the OrganisationController does the magic of adding a tenant.

[...]
def save() = {

     def myOrganisation = new Organisation(params)

// Sorry Confidental ... ;)

LoginController

The LoginController class is responsible for authorisation. This authorisation has to be multitenant aware!

import grails.plugin.multitenant.core.util.TenantUtils

class LoginController {

[...]

/**
 * Show the login page.
 */
def auth = {

// Sorry Confidental ... ;)
 
}