Difference between revisions of "Grails Plugin - Multi Tenant"

From Blue-IT.org Wiki

(Created page with "An introduction on the concept of multitenant can be found at: * [http://en.wikipedia.org/wiki/Multitenancy http://en.wikipedia.org/wiki/Multitenancy] * [http://www.itwissen.info...")
 
 
(13 intermediate revisions by the same user not shown)
Line 17: Line 17:
  
 
Later on you can use ''<nowiki>tenant[dot]</nowiki>'' to refer to the TenantMap domain class (see later on).
 
Later on you can use ''<nowiki>tenant[dot]</nowiki>'' 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 ==
 
== 2. Declare Tenant Class ==
  
This class should be a top level class. E.g. "Organisation" hasMany "Users".
+
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;
 
  import grails.plugin.multitenant.core.groovy.compiler.MultiTenant;
Line 62: Line 72:
 
     def init = { servletContext ->
 
     def init = { servletContext ->
 
    
 
    
 +
        // This refers to Config.groovy: ''grails.tenantDomain  = 'myweb1'''
 
         def domainName = ConfigurationHolder.config.grails.'''tenantDomain'''
 
         def domainName = ConfigurationHolder.config.grails.'''tenantDomain'''
        if {
+
   
          '''DomainTenantMap.findByName('myweb1')'''
+
def myweb1Tenant = DomainTenantMap.findByName('myweb1_dev')
        } else {
+
          new DomainTenantMap(
+
if ( myweb1Tenant ) {}
                      '''name:'myweb1'''',
+
else {
                      '''domainName:'sub.' + domainName''',
+
  myweb1Tenant = new DomainTenantMap(
                      '''mappedTenantId:1''').save(flush:true);
+
  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 ''domainName'' refers to the correspondent field in the database ''domain_name''.
 
The property ''mappedtenantId '' to the field name.
 
The property ''mappedtenantId '' to the field name.
Line 79: Line 99:
 
  | id | version | domain_name    | mapped_tenant_id | name          |
 
  | id | version | domain_name    | mapped_tenant_id | name          |
 
  +----+---------+-----------------+------------------+----------------+
 
  +----+---------+-----------------+------------------+----------------+
  |  1 |      0 | domain.tld      |                1 | myweb1        |
+
  |  1 |      0 | myweb1          |                1 | myweb1_dev    |
 
  [...]
 
  [...]
  
You can use '''''sub.'''domain.tld'' by concatenating the strings when setting domainName
+
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 ... ;)
 +
 
 +
}
 +
 
 +
[[Category:Java, Groovy and Grails]]

Latest revision as of 19:43, 8 January 2012

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 ... ;)
 
}