1. Overview

1.1. What is a multi-tenant architecture?

A multi-tenant architecture is an application that supports simultaneous access by multiple tenants, each with separate resources and data, and is completely isolated from each other. In layman’s terms, multi-tenancy is the process of “partitioning” an application into multiple independent instances, each without interfering with the other, according to the needs of the customer.

1.2. Advantages of multi-tenant architecture

  1. It can better meet the individual needs of different tenants.
  2. It can reduce the cost of operation and maintenance, and reduce the investment in hardware, network and other infrastructure.
  3. Saves development costs by reusing code and quickly bringing new tenant instances online.
  4. Enhanced scalability and scalability of the system, supporting horizontal scaling, with data and resources of each tenant managed and controlled.

1.3. Technology choices for implementing a multi-tenant architecture

It is not the technology that is most important for implementing a multi-tenant architecture. What is most important is the right architectural thinking. But choosing the right technology can lead to a faster implementation of a multi-tenant architecture.

2. Ideas

2.1. Architecture selection

Spring Boot and Spring Cloud are recommended for developing multi-tenant applications based on Java; Spring Boot allows for quick application builds and provides many mature plugins, while Spring Cloud provides many tools and components for implementing microservice architectures.

2.1.1. Spring Boot

Using Spring Boot simplifies the process of building projects by automatically configuring many common third-party libraries and components, reducing the workload of developers.

1
2
3
4
5
6
7
8
@RestController
public class TenantController {

    @GetMapping("/hello")
    public String hello(@RequestHeader("tenant-id") String tenantId) {
        return "Hello, " + tenantId;
    }
}

2.1.2. Spring Cloud

Spring Cloud is more useful when architecting multi-tenant systems and provides proven solutions such as Eureka, Zookeeper, Consul etc. to implement microservices such as service discovery and load balancing.

2.2. Database Design

In a multi-tenant environment the database must store data separately for each tenant and ensure data isolation. We usually achieve this in two ways:

  1. multiple tenants share the same database, with each table containing a tenant_id column to differentiate the data for different tenants.
  2. create separate databases for each tenant, each with the same table structure, but with data segregated from each other.

2.3. Application Multi-tenancy Deployment

In order to implement multi-tenancy we need to consider the following two issues when deploying applications.

2.3.1. Application Isolation

In a multi-tenant environment different tenants need access to different resources, so application isolation is required. This can be achieved by building separate containers or virtual machines, using namespaces, etc. Docker is a very popular technology for isolating containers.

2.3.2. Application Configuration

As each tenant has its own configuration requirements it is necessary to set up separate application configuration information for each tenant, such as port numbers, SSL certificates etc. These configurations can be stored in a database or in the cloud configuration centre.

2.4. Tenant Management

In a multi-tenant system it is necessary to be able to manage the data and resources of different tenants and to assign the appropriate rights to each tenant. The solution usually consists of the following two parts.

2.4.1. Tenant information maintenance

The maintenance of tenant information includes adding, modifying, deleting and querying, and requires the ability to quickly find the corresponding tenant information based on the tenant name or tenant ID.

1
2
3
4
5
6
7
CREATE TABLE tenant (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50) NOT NULL UNIQUE,
    description VARCHAR(255),
    created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

2.4.2. Tenant Permission Control

Access to system resources must be set separately for each tenant in a multi-tenant application. For example, tenant A and tenant B cannot access each other’s data.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/api/tenant/**").hasRole("ADMIN")
                .anyRequest().authenticated()
                .and()
                .formLogin();
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService())
                .passwordEncoder(new BCryptPasswordEncoder())
                .and()
                .inMemoryAuthentication()
                .withUser("admin")
                .password(new BCryptPasswordEncoder().encode("123456"))
                .roles("ADMIN");
    }
}

3. Technical Implementation

3.1. Multi-tenancy implementation in Spring Boot

Multi-tenancy mechanisms can be implemented in Spring Boot through multiple data sources and dynamic routing.

3.1.1. Multiple data sources implementation

Multiple data sources means configuring different data sources for different tenants, so that each tenant can access its own independent data. This is implemented as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
@Configuration
public class DataSourceConfig {
    @Bean(name = "dataSourceA")
    @ConfigurationProperties(prefix = "spring.datasource.a")
    public DataSource dataSourceA() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "dataSourceB")
    @ConfigurationProperties(prefix = "spring.datasource.b")
    public DataSource dataSourceB() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "dataSourceC")
    @ConfigurationProperties(prefix = "spring.datasource.c")
    public DataSource dataSourceC() {
        return DataSourceBuilder.create().build();
    }
}

The above code is configured with three data sources corresponding to each of the three tenants. The data sources that need to be connected to can then be marked using annotations when in use.

1
2
3
4
5
6
7
8
@Service
public class ProductService {
    @Autowired
    @Qualifier("dataSourceA")
    private DataSource dataSource;

    // ...
}

3.1.2. Dynamic routing implementation

Dynamic routing refers to dynamically switching to the data source of the corresponding tenant based on the URL or parameters of the request. The specific implementation is as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return TenantContextHolder.getTenantId();
    }
}

@Configuration
public class DataSourceConfig {
    @Bean(name = "dataSource")
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource dataSource() {
        return DataSourceBuilder.create().type(DynamicDataSource.class).build();
    }
}

The above is the core code of DynamicDataSource which inherits from AbstractRoutingDataSource and dynamically obtains the tenant ID through the determineCurrentLookupKey() method and then switches to the corresponding data source.

3.2. Multi-tenancy implementation in Spring Cloud

Multi-tenancy mechanisms can be implemented in Spring Cloud through service registration and discovery, configuration centres, load balancing and more.

3.2.1. Service registration and discovery

Service registration and discovery is implemented using Eureka in Spring Cloud. Each tenant’s services are registered in the registry under a different application name, and clients can access the corresponding tenant’s services by service name.

3.2.2. Configuration centre

Spring Cloud Config is used as the configuration centre. Configuration files are distinguished by tenant ID and clients get configuration information by reading the configuration file of the corresponding tenant.

3.2.3. Load balancing

Use the Spring Cloud Ribbon as a load balancer. A service instance of the corresponding tenant is selected for request forwarding based on the URL or parameters of the request.

3.2.4. API

The multi-tenant mechanism is implemented at the API gateway level to determine the tenant to which a request belongs based on the URL or parameters of the request and forward it to the service instance of the corresponding tenant.

4. Application scenarios

4.1. Private Cloud Environment

A private cloud environment is a cloud environment built by the enterprise, which is not provided to the public and is mainly used for data storage, management, sharing and security control within the enterprise. Compared with the public cloud, the advantage of the private cloud is that it can better protect the core data of the enterprise, and can also meet the requirements of the enterprise for data security and controllability.

4.2. Public Cloud Environment

Public cloud environment refers to the cloud environment built by cloud service providers and provides services to the public. Users can purchase corresponding cloud services such as cloud storage, cloud computing, cloud database, etc. according to their needs. Compared to private clouds, public clouds have the advantages of low cost, elasticity and scalability, global deployment, etc., and can better meet the needs of rapid development of enterprises.

4.3. Enterprise-level applications

Enterprise-level applications are applications for enterprise customers, mainly including ERP, CRM, OA and a series of other applications. These applications are characterised by powerful, complex processes and large data volumes, and need to meet the enterprise’s requirements for high efficiency, high reliability, high security and easy maintainability. In the cloud computing environment, enterprises can deploy these applications on private or public clouds, reducing the investment in hardware equipment and maintenance costs and improving management efficiency.

5. Implementation Steps

5.1. Build the Spring Boot and Spring Cloud environment

First you need to add the following dependencies to the Maven project:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<!-- Spring Boot -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- Spring Cloud -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-dependencies</artifactId>
    <version>2020.0.3</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>

The appropriate parameters then need to be configured in application.yml, as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/appdb?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    username: root
    password: 123456

mybatis:
  type-aliases-package: com.example.demo.model
  mapper-locations: classpath:mapper/*.xml

server:
  port: 8080

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

management:
  endpoints:
    web:
      exposure:
        include: "*"

where datasource.url is the URL of the database connection, username and password are the account and password for the database connection; server.port is the port on which the Spring Boot application is launched; and eureka.client.serviceUrl. defaultZone is the URL of the Eureka service registry.

5.2. Modifying the database design

Next, the database needs to be modified accordingly to support multi-tenant deployments. Specifically, we need to add a tenant-related field to the database in order to distinguish between different tenants in the application.

5.3. Implementing multi-tenant deployment of an application

The next step is to implement multi-tenant deployment of the application in code. Specifically, we need to instantiate a corresponding Spring bean for each tenant and route requests to the appropriate bean based on the tenant ID.

The following is a simple example implementation:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@Configuration
public class MultiTenantConfig {
 
    // Provide data sources for the corresponding tenants
    @Bean
    public DataSource dataSource(TenantRegistry tenantRegistry) {
        return new TenantAwareDataSource(tenantRegistry);
    }
 
    // Multi-tenant Session Factory
    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource)
            throws Exception {
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);
        return sessionFactory.getObject();
    }
 
    // Dynamic tenant switching
    @Bean
    public MultiTenantInterceptor multiTenantInterceptor(TenantResolver tenantResolver) {
        MultiTenantInterceptor interceptor = new MultiTenantInterceptor();
        interceptor.setTenantResolver(tenantResolver);
        return interceptor;
    }
 
    // Register Interceptor
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(multiTenantInterceptor());
    }
 
    // Registered Tenant Information
    @Bean
    public TenantRegistry tenantRegistry() {
        return new TenantRegistryImpl();
    }
     
    // Resolving Tenant IDs
    @Bean
    public TenantResolver tenantResolver() {
        return new HeaderTenantResolver();
    }
 
}

The core configuration class for multi-tenant deployments is MultiTenantConfig, which provides functions such as corresponding tenant data sources, multi-tenant session factories, dynamic tenant switching, etc.

5.4. Implementing tenant management

Finally, a tenant management feature needs to be implemented in order to manage the different tenants in the system. Specifically, we can use Spring Cloud’s service registration and discovery component, Eureka, to register each tenant instance and perform the appropriate actions in the management interface. In addition, we need to provide a separate database for each tenant to ensure data isolation.

6. Summary

This article has detailed how to implement a multi-tenant deployment enabled application using Spring Boot and Spring Cloud. This includes setting up the Spring Boot and Spring Cloud environment, modifying the database design, implementing multi-tenant deployment of the application, and implementing tenant management. Application scenarios include SaaS applications, multi-tenant cloud services, etc. The advantages and disadvantages are mainly in terms of improving the scalability and maintainability of the application, but also increasing the complexity of deployment and management. Future directions for improvement could be considered to further enhance the automation of multi-tenancy management and reduce manual intervention and error rates.

Reference: https://blog.csdn.net/u010349629/article/details/130737253