2 Plugin concepts - Reference Documentation
Authors: Kim A. Betti
Version: 0.8.3
Table of Contents
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 likeUser
, 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.Tenantclass 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 } }}
Dynamic methods
The plugin adds a few methods to yourTenant
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 towithThisTenant
, 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. }
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() }
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 thegrails.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
import grails.plugin.multitenant.core.resolve.TenantResolverclass 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 } }}
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
ThetenantRepository
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.