The term multi-tenancy refers to software architecture in which a single instance of a software application serves multiple customers. Each customer is called tenant.
In a multi-tenancy environment, multiple customers share the same application which is deployed on a common set of software and hardware resources, with the same data-storage mechanism. One important aspect that should be provided in the application design is the requirement that customers shall not share or see each other’s data.
Taking this in mind, tenants may be given the ability to customize some parts of the application, such as color of the user interface (UI) or business rules, but they cannot customize the application code.
Multi-tenancy is more viable from an economic perspective because the software development and maintenance costs are shared among the customers. With multi-tenancy architecture, the software provider makes updates only once. With single-tenancy architecture, the provider has to maintain multiple, per customer specific instances of the software in order to make regular updates.
The main focus of multi-tenancy architecture is to build an application that can make distinction between shared data and isolated data used by the tenants.
- Shared data is used across the whole application regardless of the tenant. Example for shared data can be list of users, user roles, countries, business processes etc.;
- Isolated data is tenant specific, all the tenants have the same structure of the database tables, but the data itself is different for each tenant.
A simple example of a multi-tenant WEB application in Java where database layer distinguishes among several tenants, can be realized with combination of ThreadLocal variable and Hibernate’s multi-tenant support.
Once the user logs into the application, for each HTTP request, ThreadLocal static variable (userHolder) will be populated with the current user’s info. This holder variable will contain information about the user’s tenant as well. In our particular case, the holder will keep information about the tenant’s code which equals to the tenant’s specific database schema. The userHolder variable is available elsewhere in the code.
The next step is to implement the Hibernate’s CurrentTenantIdentifierResolver interface and the method resolveCurrentTenantIdentifier therein. This method will get and use the tenant code from the current userHolder variable in order to help Hibernate obtain tenant specific database connections.
By default, if the userHolder is empty (not authenticated user) or if the tenant is not populated in the userHolder (system administrator), the resolveCurrentTenantIdentifier method will return some default database schema (system, public etc.)
The implementation of the Hibernate’s CurrentTenantIdentifierResolver (tenantIdentifierResolver) can be specified via the hibernate.tenant_identifier_resolver JPA setting. For example, Spring Boot JPA properties configuration could be:
Let’s assume that in our example we use PostgreSQL database, where each database can hold multiple schemas. Our multi-tenant architecture is based on isolated tenant schemas inside the same database. The MultiTenantConnectionProvider interface (tenantConnectionProvider) implementation provides the following tenant or system specific database connections:
The concept explained in this post is simple, but yet powerful for building SaaS applications where tenant specific data is isolated into separate database schemas. Besides security, data isolation allows us to address some client SLA parameters regarding query performance and metrics. Simply speaking, tenant having a small data set will query that small data set only, not the whole application data that is not necessarily proportionally distributed among the tenants.