Monday, March 5, 2012

Setting up a multi tenant environment using the Spring Framework

In this post I'll describe how to set up a multi tenant application using the Spring Framework (I am using Spring 3).
I'll use a pure Java application, but the concepts work well in enterprise apps.

The requirements are simple:
  • Single application to serve all the tenants
  • Every tenant uses the same wiring, each tenant has different properties 
  • Have common properties and wiring that can be shared by all the tenants
    • Allow each tenant to override the common settings
The solution:
  • Create a Spring ApplicationContext for every tenant
    • Set the PropertyConfigurer in runtime
Let's see this in action:

public class MultiTenantSpringExample {
 
 private ClassPathXmlApplicationContext commonCtx; // use ClassPathXmlApplicationContext instead of ApplicationContext so we can destory them
 private List tenantContexts;

 public void init( List tenants ) {

  tenantContexts = new ArrayList( tenants.size() );
  // create a common application context, shared among all the tenants
  commonCtx = new ClassPathXmlApplicationContext( "/commonContext.xml" );
  // set up all the tenants
  for ( String tenant : tenants ) {
   // for each tenant create a Spring ApplicationContext
   ClassPathXmlApplicationContext tenantCtx = new ClassPathXmlApplicationContext();
   tenantCtx.setParent( commonCtx );
   tenantCtx.setConfigLocation( "/tenantContext.xml" );
   TenantPropertyPlaceholderConfigurer beanFactoryPostProcessor = new TenantPropertyPlaceholderConfigurer( tenant );
   tenantCtx.addBeanFactoryPostProcessor( beanFactoryPostProcessor );
   tenantCtx.refresh();
   tenantContexts.add( tenantCtx );
  }
 }
 
 public void destroy() {
  // destroy the tenant contexts
  for ( ClassPathXmlApplicationContext tenantContext : tenantContexts ) {
   tenantContext.destroy();
  }
  tenantContexts.clear();
  // destroy the common context
  commonCtx.destroy();
 }
 
 public static void main( String[] args ) {
  MultiTenantSpringExample example = new MultiTenantSpringExample();
  example.init( Arrays.asList( "a", "b" ) );
  example.destroy();
 }
}

On line 18 I create the common wiring, that is shared among tenants, and set it as the parent for the tenant application context on line 23.
On line 25-27 I inject the PropertyPlaceholderConfigurer, which is created differently for every tenant.

public class TenantPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer {
 public TenantPropertyPlaceholderConfigurer( String tenant ) {
  super();
  setIgnoreResourceNotFound( true ); // this makes the common file and tenant file optional
  // prepare the default properties
  String defaultPropertiesResourcePath = "/global.properties";

  Resource defaultPropertiesResource = new ClassPathResource( defaultPropertiesResourcePath );
  // prepare tenant properties
  String tenantPropertiesResourcePath = '/' + tenant + ".properties";
  Resource tenantPropertiesResource = new ClassPathResource( tenantPropertiesResourcePath );
  // set the locations
  Resource[] locations = new Resource[] { defaultPropertiesResource, tenantPropertiesResource };
  setLocations( locations );
 }
}


The TenantPropertyPlaceholderConfigurer uses classpath resources, using a common properties file, shared for all the tenants, and a per-tenant properties file.

To complete the example I created a simple class, A, holding an int, and printing the int in the print() method.
public class A {
 private int i;
 
 public void setI( int i ) {
  this.i = i;
 }
 
 public int getI() {
  return i;
 }
 
 public void print() {
  System.out.println( "Example property: " + i );
 }
}


And the resource files: commonContext.xml, empty in the example but can be used for sharing wiring among tenants
<beans>
</beans>


tenantContext.xml, creates an instance of A for every tenant, each one with different property values, and calls the print() method after constructing the object to print the value.
<beans> 
 <bean class="A" id="a" init-method="print">
  <property name="i" value="${tenant-i}">
 </property></bean>
</beans>


And two matching properties files: a.properties
tenant-i = 1

b.properties
tenant-i = 2

When executing the 'main' method the program outputs "1" for tenant 'a' and "2" for tenant 'b'