Implementing Multi-tenancy On Google App Engine


Google recently added support for multi-tenancy for applications via the Namespaces API. We are in process of porting an application to Google App engine. We were thinking of making our application multi-tenant. With the new Namespaces API in our arsenal we decided to give it a try. To our surprise implementing multi-tenancy is easy, we were done with it in couple of hours!

With multi-tenancy, multiple client organizations (or “tenants”) can all run on a same hosted application. In effect due to segregation of data using a unique namespace for each client. This allows us to serve the same application to different customers, with each customer seeing their own unique copy of the application.

Let’s see how we went about implementing it. First of all we needed a Filter it sets unique name-space for each request. Here is the code listing.

public class NameSpaceFilter implements Filter {

	@Autowired
	private SeedData seedDataSetup;

	private final static Logger logger = Logger.getLogger(NameSpaceFilter.class);

	/**
	 * Enumeration of namespace strategies.
	 */
	enum NamespaceStrategy {
		/**
		 * Use the server name shown in the http request as the namespace name.
		 */
		SERVER_NAME,

		/**
		 * Use the Google Apps domain that was used to direct this URL.
		 */
		GOOGLE_APPS_DOMAIN,

		/**
		 * Use empty namespace.
		 */
		EMPTY
	}

	// The strategy to use for this instance of the filter.
	private NamespaceStrategy strategy = NamespaceStrategy.SERVER_NAME;

	// The filter config.
	private FilterConfig filterConfig;

	public void init(FilterConfig config) throws ServletException {
		this.filterConfig = config;
		String namespaceStrategy = config.getInitParameter("namespace-strategy");
		if (namespaceStrategy != null) {
			try {
				strategy = NamespaceStrategy.valueOf(namespaceStrategy);
			} catch (IllegalArgumentException exception) {
				// Badly configured namespace-strategy
				filterConfig.getServletContext().log(
						"web.xml filter config "namespace-strategy" setting " + "to "" + namespaceStrategy
								+ "" is invalid. Select " + "from one of : "
								+ Arrays.asList(NamespaceStrategy.values()).toString());
				throw new ServletException(exception);
			}
		}
	}

	public void destroy() {
		this.filterConfig = null;
	}

	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
			ServletException {

		// If the NamespaceManager state is already set up from the
		// context of the task creator the current namespace will not
		// be null. It's important to check that the current namespace
		// has not been set before setting it for this request.
		if (NamespaceManager.get() == null) {
			switch (strategy) {
			case SERVER_NAME: {
				NamespaceManager.set(request.getServerName());
				HttpSession httpSession = ((HttpServletRequest) request).getSession();
				if (StringUtils.isEmpty((String) httpSession.getAttribute("isSeedDataSet"))) {
					seedDataSetup.setSeedData();
					httpSession.setAttribute("isSeedDataSet", "true");
				}
				break;
			}

			case GOOGLE_APPS_DOMAIN: {
				NamespaceManager.set(NamespaceManager.getGoogleAppsNamespace());
				break;
			}

			case EMPTY: {
				NamespaceManager.set("");
			}
			}
		}

		// chain into the next request
		chain.doFilter(request, response);
	}
}

This filter class defines an Enum for namespace strategy which our application is going to use to provide unique namespace to our tenant. Filter also has an init() method which sets the namespace strategy. If you look at the doFilter() method closely you will notice that depending on the namespace strategy we either set NamespaceManager to server-name, google apps namespace or an empty string which signifies a default namespace.

Mapping of the filter class in web.xml, here we provide the namespace-strategy of using server name. Here is how our web.xml looks like.

<web-app>
   .
   .
<filter>
   <filter-name>nameSpaceFilter</filter-name>
   <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
   <init-param>
      <param-name>namespace-strategy</param-name>
      <param-value>SERVER_NAME</param-value>
   </init-param>
</filter>
<filter-mapping>
   <filter-name>nameSpaceFilter</filter-name>
   <url-pattern>/eh/*</url-pattern>
</filter-mapping>
   .
   .
</web-app>

In our application we need to populate seed data and now it is the filter’s responsibility to populate it for a single time. In the switch-case for SERVER_NAME, filter not only sets the NamespaceManager but also sets the seed data and a session variable.

In the web.xml listing you will notice that we used DelegatingFilterProxy as filter-class instead of NamespaceFilter class we defined above. DelegatingFilterProxy class allows us to inject spring managed beans in our filter. The only requirement being that filter-name should be set to bean id definition our filter in Spring context file and filter class should implement javax.servlet.Filter interface. You can read more about it here.

Here is the code listing of our spring context file.

<bean id="nameSpaceFilter" class="com.inphina.ehour.authorization.NameSpaceFilter" />

When we set proper namespace in the namespace manager, all data-store queries in our Data access layer will use the namespace set in name space manager for CRUD operations. This effectively segregates the data for each tenant. It also means we need not change any other code to implement multi-tenancy in our application.

Advertisements
This entry was posted in Agile, Cloud, Java and tagged , , . Bookmark the permalink.

4 Responses to Implementing Multi-tenancy On Google App Engine

  1. Pingback: Multi-tenancy in Google App Engine: Scope of NamespaceManager « Inphina Thoughts

  2. Pingback: Comparing Google App Engine and Amazon EC2 on Technology « Inphina Thoughts

  3. Pingback: Inphina Thoughts

  4. Kiran says:

    getGoogleAppsNamespace and getServerName return the website’s domain name. If the domain name changes this solution will not work. If you agree, can you propose a way to address this?
    Thanks

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s