(Quick Reference)

2 Plugin concepts - Reference Documentation

Authors: Kim A. Betti

Version: 0.8.3

2 Plugin concepts

Central concepts.

2.1 Tenant class

Most applications will have a domain class where you store tenant data. This will probably be named something like User, Customer or Tenant. By implementing the interface grails.plugin.multitenant.core.Tenant and overriding the Integer tenantId() method, the plugin will give you some benefits out of the box.

The examples below assumes that you have a tenant class similar to this.

import grails.plugin.multitenant.core.Tenant

class Customer implements Tenant {

String name

Integer tenantId() { return this.id }

}

Automatic tenant events

The plugin will trigger a few custom events when changes are made to your tenant domain class. This can be very useful for things like maintaining a cache for fast lookups.

The following events are published when a tenant has been added, updated or deleted: tenant.created, tenant.updated and tenant.deleted.

class MyTenantService implements InitializingBean {

def eventBroker

void afterPropertiesSet() { eventBroker.subscribe("tenant.createed") { evt -> println "New tenant created: " + evt.payload } }

}

Have a look at the documentation for Hawk Eventing for information on how to subscribe to these events.

Dynamic methods

The plugin adds a few methods to your Tenant class.

withThisTenant

Normally you want as much as possible of the multi-tenant stuff to happen transparently behind the scenes, but there are cases where it's useful to be able to run operations on a specific tenant. This is often useful during testing and bootstrapping of the application during development.

def customerX = new Customer(name: "Customer X")
customerX.save()

customerX.withThisTenant { // All Hibernate related operations executed here // will happen with 'Customer X' as current tenant. }

withTenantId

This is method is similar to withThisTenant, but is added to the static meta class. It's useful when you don't have the tenant instance available, but you know the tenant-id.

Customer.withTenantId(123) {
    // All Hibernate related operations executed here
    // will be executed with tenant id 123. 
}

This method is added to the Tenant interface either, so, whatever you Tenant class is named (Customer, User, etc), you can always use:

Tenant.withTenantId(123) {
    // All Hibernate related operations executed here
    // will be executed with tenant id 123. 
}

withoutTenantRestriction

This method does the opposite of others. The plugin executes the closure passed as parameter without any tenant restrictions. For example, if you're running on tenant 1, and run the following code:

def messages = []

Customer.withoutTenantRestriction { // All Hibernate related operations executed here // will be executed without any tenant restriction. messages = Message.list() }

It will return all messages in the database, even Message being a @MultiTenant annotated class.

This method, as withTenantId, is added to the Tenant interface, so, whatever you can use:

Customer.withoutTenantRestriction {
    // All Hibernate related operations executed here
    // will be executed without any tenant restriction.
}

2.2 Multi tenant domain classes

The @MultiTenant annotation

The @MultiTenant annotation is a central part of this plugin. This annotation causes two things to happen.

The annotation is part of a AST transformation hooking into the compilation process adding a Integer tenantId field to all annotated domain classes. The transformation also adds the MultiTenantDomainClass interface if the domain class isn't already implementing it.

The application will look for Hibernate domain classes annotated with @MultiTenant during application startup. Hibernate operations against these domain classes will be filtered to only affect data belonging to the current tenant.

import grails.plugin.multitenant.core.annotation.MultiTenant

@MultiTenant class Animal {

String name

}

The unique constraint

You must take some extra care with the unique constraint as all tenants will share the same database. In the future we might add this automatically during application startup.

import grails.plugin.multitenant.core.annotation.MultiTenant

@MultiTenant class Animal {

String name

static constraints = { name unique: 'tenantId' }

}

2.3 Tenant resolver

The tenant resolver is point on your application that will detect witch tenant will deal the current request that is made on your application. Most multi-tenant applications would define tenant based on the URL used to reach your application, but you can check for some user's session attribute, or any other thing that you want.

When implementing your own tenant resolver, you'll have to implement the grails.plugin.multitenant.core.resolve.TenantResolver interface and override the public Integer resolve(HttpServletRequest request) method.

For example, imagine you have the '*.greatapp.com' hosting your application.

  • Requests coming from 'john.greatapp.com' would resolve the tenant for user john, so tenant 1
  • Requests coming from 'mary.greatapp.com' would resolve the tenant for user mary, so tenant 2
  • Requests coming from 'paul.greatapp.com' would resolve the tenant for user paul, so tenant 3

The code below shows how a simple Tenant Resolver implementation should be.

import grails.plugin.multitenant.core.resolve.TenantResolver

class TenantDomainResolver implements TenantResolver {

@Override public Integer resolve(HttpServletRequest request) {

String host = request.getServerName() switch (host) { case "john.greatapp.com": return 1

case "mary.greatapp.com": return 2

case "paul.greatapp.com": return 3

default: return null } }

}

Of course, your application will probably look this information up from a database. Host names rarely change so if you resolve tenants based on the host http header you might want to implement a host => tenant-id cache here.

In the example above, if the tenant has not been found, it would return null. Be careful with this approach, returning null, query restrictions will not be applied, so, data of all tenants will be returned when queried.

2.4 Tenant repository

The tenantRepository bean's responsibility is to look up Tenant instances based on the numeric tenant id (provided by the tenantResolver bean stored in currentTenant). Running the mt-quickstart script will generate a basic implementation for you.

2.5 Tenant scoped beans

The plugin ships with experimental support for per-tenant beans. It's implemented using a custom tenant scope in combination with a scoped proxy.

Configured in config.groovy:

This feature can be configured through config.groovy. See example below:

multiTenant {
    perTenantBeans = [ "someSpringBean" ]
}

The static scope property in Grails services

This will only work for Grails service artifacts.

class MyService {

static scope = "tenant"

}

Spring's @Scope annotation

Spring 2.5 introduced a @Scope annotation, but this seems to be tied to component scanning and will not work out of the box with the custom tenant scope introduced by this plugin.