Customization within Multi-Tenant Platforms
Using a cloud-based multi-tenant architecture for your software product works great. Every one of your customers is served from a single installation. Upgrades are simpler to perform compared to an on-premise operation model. It might even be the case that only a single version of your software is installed in your entire environment.
It all looks great. Then one day the whole team meets to plan the next iteration of the product. The conversation could start like this: “Good job guys pushing out the new release yesterday! It’s fantastic to use our new search functionality. Let’s release it to beta users next week. And to paying customers at the end of the month.”
As outlined in the beginning of this article using a multi-tenant platform is beneficial from an operators point of view. However, the price one has to pay lies in the increased complexity. The same installation will need to be customized in terms of branding, work flow, or even access control. At Squirro we are building exactly such a multi-tenant platform to serve all of our customers. In order to e.g. customize a certain work flow there is a central service which allows configuration values to be stored in an efficient and hierarchical manner.
What works great is to exploit the fact that configuration values can be the same for a set of users. You can for example define which logo to show in your front end web application for all users of a single customer. For storage and maintenance reasons you are better off to not store this information for every user individually. However, the hierarchical nature of the system still accounts for the fact that a single user might have a different need. In the picture below you can see how configuration values are organized. At the very top there is always a default entry for any configuration value that is stored within the system. Below that users can be grouped into tenants and groups. At the bottom level a value can be present for a single user.
As our architecture is based on independent HTTP services the configuration values stored on persistent storage are exposed via a RESTful interface. Any module that needs to fetch values from the configuration service uses our Python client library. It abstracts away all the boiler plate HTTP calls and the corresponding error handling routines. Moreover, the library contains a time-based cache which expires cached entries after a given time period. Such a design makes sure that on one hand the service is not contacted on every request to get the same configuration value. On the other hand a time based cache makes sure that updated values on stable storage get propagated eventually.
So let us walk through an example and see how things look in actual Python code. The goal is to query the configuration service to check whether the improved search functionality is enabled for a particular user. First we import our client library and create a new client. Then we can query the service and make sure that configuration values are stored within an in-memory cache of the current process.
The code example above shows how to get the default value which is used as a feature switch. But now the hierarchy of the system allows us to store values for all users of an tenant. Or we can fine tune the setting for an individual user. In the code example below it is shown that the new search functionality has been enabled for a particular software engineer who is currently testing the new feature. All the other members of the team get the value which is defined for the tenant.
There are many use cases for the usage of a configuration service within a multi-tenant environment. The scenario of work flow customization has been highlighted above. Taking advantage of the hierarchical nature of configuration values is crucial to make such a service scale and be used throughout a multi-tenant platform.