- What is Multitenancy?
- Multi-tenancy is a term that is used often in the context of SaaS applications. In general, multi-tenancy refers to the ability to run multiple client of an application on a shared infrastructure.
- The main motivation for doing this is efficiency, or in other words — reducing the cost per client in comparison to a dedicated system where each client has their own dedicated environment.
- A multitenant application is a shared resource that allows separate client or “tenants” to view the application as though it was their own.
- A typical scenario that lends itself to a multitenant application is one in which all client of the application may wish to customize the user experience but otherwise have the same basic business requirements.
2. Multitenancy Architecture
There are many approaches and levels of multi-tenancy, depending on the application layer and type of application you’re dealing with. We can implement multi-tenancy using any of the following approaches:
- Separate databases: Each tenant has its own database
- Separate schemas: Tenants share common database but each tenant has its own set of tables (schema)
- Shared schema: Tenants share common schema and are distinguished by a tenant discriminator column
3. Benefits of Multi-tenancy
i. Cost efficiency
Sharing of resources, databases, and the application itself means lower costs per customer. There is no need to buy or manage additional infrastructure or software
ii. Fast, easy deployment
With no new infrastructure to worry about, set-up and on boarding are simple.
iii. Scaling
Each database tenant can be distributed and scale dynamically across multiple machines, if necessary, to meet demand. This includes moving data to other machines if necessary. Scaling must be supported seamlessly without any downtime.
4. Multi-tenant Challenges and Solutions
i. Data Isolation
A multi tenant database necessarily sacrifice the data isolation. The data of multiple tenants is stored together in one database. There is always a risk of exposing the one tenant data to another tenant
Solutions
During development, ensure that queries never expose data from more than one tenant.Tenant specific important information should be available based on their privileges with strict authentication.
ii. Performance and Scalibility
A multi-tenant database shares compute and storage resources across all its tenants. Therefore, the multi-tenant database carries an increased risk of encountering noisy neighbors, where the workload of one overactive tenant impacts the performance experience of other tenants in the same database.
Solutions
Additional application-level monitoring could monitor tenant-level performance.
Have a tool to perform regress test and identify possible area where database resource getting utilize by application and reduce the cause to avoid performance issue.
iii. Security and Privacy
A multi-tenant database shares resources across all its tenants. Because of which tenant might face security and privacy concerns. Even there will change of SQL attacks/injection due to which hacker might still sensitive data or make changes in data stored in databases.
Solutions
A multi-tenant database make each tenant secure by having proper authentication and authorization. This also help in preventing SQL attack/injection.
5. Implementation
We can choose any programming paradigm to implement all the three approaches of multi-tenancy. In this blog, I chose to implement schema per tenant architecture using spring, ldap and hibernate with one connection pool for all tenants.
In order to understand how multi-tenant application work, we will going to have simple login screen, upon login will going to display information of specific tenant.
To handle login, we will use ldap to get user tenant identifier’s upon login where the tenant is still unknown. Then, the tenant identifier uses to access the database.
In order to access the database we are going to use Hibernate which is a Java Persistent API (JPA) Implementation. We have chosen Hibernate because it is a well-known ORM which has provided support for multi-tenancy implementation since version 4.
To use Hibernate with your Java application all you need to do is to implement two interfaces: MultitenantConnectionProvider and CurrentTenantIdentifierResolver.
There are two approaches for implementation using hibernate session default and using hibernate custom implementation.
Using hibernate custom implementation
In our example we are going to use custom implementation of MultitenantConnectionProvider. Our implementation provides a different connection by tenantId. This connection is then used for the Hibernate session. To provide tenantId for session creation we must implement CurrentTenantIdentifierResolver, this class has a method called resolveCurrentTenantIdentifier() which is internally called by hibernate.
i. To have MultitenantConnectionProvider implementation we must create class and implement any of below classes:
- AbstractDataSourceBasedMultiTenantConnectionProviderImpl
- AbstractMultiTenantConnectionProvider
- DataSourceBasedMultiTenantConnectionProviderImpl
For our purpose, we want to provide connections to different databases through several DataSources. For that we could use a map<TenantId, DataSource>, in this way we’d get the DataSource by the tenantId.
Also, we could extend DataSourceBasedMultiTenantConnectionProviderImpl but it uses jndi. As we will not use jndi in this post, we implement AbstractDataSourceBasedMultiTenantConnectionProviderImpl instead:
// Code
@PostConstruct
public void load() throws Exception {
private Map<String, DataSource> map = new HashMap<>();
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(“com.mysql.jdbc.Driver”);
dataSource.setUrl(“jdbc:mysql://localhost:3306/tenant_1”);
dataSource.setUsername(“root”);
dataSource.setPassword(“root”);
map.put(tenantId, createDataSource(dataSourceConfiguration));
}
@Override
protected DataSource selectAnyDataSource() {
return map.get(”default_tenant”);
}
@Override
public DataSource selectDataSource(String tenantIdentifier) {
return map.get(tenantIdentifier);
}
@Bean(name = { “dataSource” })
public DataSource selectDataSource() {
return selectDataSource(”default_tenant”);
}
ii. CurrentTenantIdentifierResolver can be implemented in different ways. In our case we decided to take the tenantId from an httpSession attribute. And this is used in throughout Hibernate in this interface.
// Code
@Override
public String resolveCurrentTenantIdentifier() {
ServletRequestAttributes requestAttributes =
(ServletRequestAttributes)
RequestContextHolder.getRequestAttributes();
HttpSession session = requestAttributes.getRequest().getSession();
if (session != null) {
String identifier = (String)
session.getAttribute(”tenant_resolver”);
if (identifier != null) {
return identifier;
}
}
}
iii. Finally, we need to create HibernateConfig class that composes the pieces and configures the LocalContainerEntityManagerFactoryBean. In LocalContainerEntityManagerFactoryBean we set the MultiTenancyStrategy as DATABASE and override the connection provider and Tenant Identifier Resolver.
// Code
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder) {
Map<String, Object> hibernateProps = new LinkedHashMap<>();
hibernateProps.putAll(dataSources);
hibernateProps.put(Environment.MULTI_TENANT, MultiTenancyStrategy.DATABASE);
hibernateProps.put(Environment.MULTI_TENANT_CONNECTION_PROVIDER, hibernateMultiTenantConnectionProvider);
hibernateProps.put(Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, hibernateCurrentTenantIdentifierResolver);
hibernateProps.put(Environment.DIALECT, ”org.hibernate.dialect.MySQLDialect”);
hibernateProps.put(Environment.SHOW_SQL, false);
return builder.dataSource(dataSource)
.packages(“com.example.entity”).properties(hibernateProps)
.jta(false).build();
}
Using hibernate session factory
The SessionFactory class used to create session, when the session is created the tenantId is specified. Through the use of the tenantId, hibernate can determine which resources to use, such that a tenant would access its (and only its) database. Below is an example of how a session is created:
Session session = sessionFactory
.withOptions()
.tenantIdentifier(yourTenantIdentifier)
…
.openSession();
Note: Since we use JPA, we don’t have to use the above code because Hibernate sessions are created automatically by JPA.
Note: Also, don’t confuse Hibernate sessions with Http sessions, one is for database transactions, the other is for a user across more than one-page request.
For full code we will provide the GitHub repo link in next blog.
References