Skip to Content
Technical Articles

MULTITENANCY – THE WAY AHEAD

In this blog, I will explain you how to implement provider managed cloud application based on Spring to support multi tenancy using HANA MDC (Multitenant Database Containers).

For those who are not much aware of multi-tenancy using HANA MDC system are advised to go through the below overview blog.

https://blogs.sap.com/2016/11/15/sap-hana-multitenant-database-architecture/

In provider managed application with HANA MDC, each customer gets a separate tenant database and thus isolation of customer’s data is achieved at database level which is a preferred option, especially for applications hosted on cloud.

There are few configuration steps needs to be performed in your SAP cloud platform account to make your application to be multi-tenant.

 

Step 1: Below screenshot 1 shows a HANA MDC database system with different tenant databases created in a single MDC system.

In this scenario, I have created 3 tenant DB’s out of which, one is for provider application (provider application is the one which the customers would be subscribed to) and other tenant DB’s are for two different customers for ex. customer ‘abc’ and customer ‘xyz’.

Note: You can create as many tenant-databases out of a single MDC system depending on the provisioning quota.

 

Screenshot 1

Step 2: Below screenshot 2 shows how the data-source bindings to be created for each tenant database.

The naming convention which I have followed in my sample application while creating data-source name is: “demo-“+ account-name. Here account-name is the sub-account name which you can see in the overview tab of HCP cockpit in the sub-account information section.

Repeat the creation of data-source bindings depending on the number of tenant databases. While creating data-source binding make sure a different database user under custom logon is maintained for each of the binding.

Screenshot 2

Step 3: After you deploy the java application in your provider account, customer needs to subscribe to this application which can be done by executing the below neo command:

To run this command, you would have downloaded SDK available in https://tools.hana.ondemand.com/ to your local machine. Navigate to the neo sdk tools path using command prompt like:

C:\xxxxx\neo-java-web-sdk-3.42.17\tools

Then give the below command which would create a subscription in the customer account for the provider application:

neo subscribe –account <customer-sub-account-name> –application <provider-sub-account-name>:<application-name> –user <user-id> –host <host-name>

Example:

neo subscribe –account wxxxxxd –application wxxxxx4:aamultitenancy –user ixxxxx –host hana.ondemand.com

Link to find available host: https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/350356d1dc314d3199dca15bd2ab9b0e.html#loiod722f7cea9ec408b85db4c3dcba07b52

 

Here, user-id could be your I-number or the P-user id. Once you run this command, it will prompt for your password, once Authentication is successful, it will create a subscription to the specified application in the neo account wxxxxxd.

As shown in screenshot 3 below it would create a subscription for the provider application in the ABC customer account and the URL shown below would be for ABC customer.

Screenshot 3

 

Implementation

 

In this sample multi-tenant application using HANA MDC model, will explain each class of the sample application:

  1. DemoApplication.java: This is the spring boot starter class.
  2. CurrentTenantResolver.java: This class is used to get the sub-account name in neo and add the required “resource-ref” for tenant context in web.xml file.
  3. import javax.naming.Context;
    import javax.naming.InitialContext;
    import javax.naming.NamingException;
    
    import org.springframework.stereotype.Component;
    
    import com.sap.cloud.account.TenantContext;
    
    @Component
    public class CurrentTenantResolver {
    
    	public String getCurrentTenantId() {
    
    		InitialContext ctx;
    		try {
    			ctx = new InitialContext();
    			Context envCtx = (Context) ctx.lookup("java:comp/env");
    			TenantContext tenantContext = (TenantContext) envCtx.lookup("TenantContext");
    			return tenantContext.getTenant().getId();
    		} catch (NamingException e) {
    			return "NOT_CURRENTLY_RUNNING_MULTI_-_TENANT";
    		}
    	}
    
    	public String getCurrentAccountName() {
    
    		InitialContext ctx;
    		try {
    			ctx = new InitialContext();
    			Context envCtx = (Context) ctx.lookup("java:comp/env");
    			TenantContext tenantContext = (TenantContext) envCtx.lookup("TenantContext");
    			return tenantContext.getTenant().getAccount().getId();
    		} catch (NamingException e) {
    			return "NOT_CURRENTLY_RUNNING_MULTI_-_TENANT";
    		}
    	}
    }
  4. DataSourceResolver.java: This Singleton class would maintain the data-source in a static context, if a new data-source is registered it would create a new key value pair to the map. Otherwise it will return the data-source name which in this case is <demo-subaccountname>.
  5. public class DataSourceResolver {
    
    	private static DataSourceResolver dataSourceResolver = null;
    	private static Map<Object, DataSource> targetDataSources = new HashMap<>();
    
    	private DataSourceResolver() {
    
    	}
    
    	public static DataSourceResolver getInstance() {
    		if (dataSourceResolver == null)
    			dataSourceResolver = new DataSourceResolver();
    		return dataSourceResolver;
    	}
    
    	public DataSource getDataSource(String accountName) throws NamingException {
    		if (!targetDataSources.containsKey(accountName)) {
    			InitialContext context = new InitialContext();
    			DataSource dataSource = (DataSource) context.lookup("unmanageddatasource:" +"demo-"+ accountName);
    			targetDataSources.put(accountName, dataSource);
    		}
    		return targetDataSources.get(accountName);
    	}
    
    }
  6. JpaConfiguration.java: JPA config file which is used in spring applications.
  7. MultiTenantConfig.java: This is the Database configuration class where you create a data-source bean using a fixed data-source but here you will be creating a dynamic data-source bean using the object of “MultitenantDataSource” class which is explained below. With multi-tenant approach we should not be creating a fixed data-source bean and a static data-source name in web.xml.
  8. @Configuration
    @ComponentScan(basePackages = { "com.example.demo", "com.*" })
    public class MultiTenantConfig {
    
    	@Bean
    	public DataSource jndiDataSource() throws NamingException {
    
    		MultitenantDataSource dataSource = new MultitenantDataSource();
    		return dataSource;
    
    	}
    }
  9. MultitenantDataSource.java: This class extends the spring provided abstract class for multi-tenancy (“AbstractRoutingDataSource”) and override required methods.
  10. public class MultitenantDataSource extends AbstractRoutingDataSource {
    
    	@Override
    	protected DataSource determineTargetDataSource() {
    		DataSourceResolver dataSourceResolver = DataSourceResolver.getInstance();
    		CurrentTenantResolver currentTenantResolver = new CurrentTenantResolver();
    		try {
    			return dataSourceResolver.getDataSource(currentTenantResolver.getCurrentAccountName());
    		} catch (NamingException e) {
    			e.printStackTrace();
    		}
    		return null;
    	}
    	
    	@Override
    	public void afterPropertiesSet(){
    		
    	}
    	
    	@Override
    	protected String determineCurrentLookupKey() {
    		CurrentTenantResolver currentTenantResolver = new CurrentTenantResolver();
    		String currentAccountName = currentTenantResolver.getCurrentAccountName();
    		return currentAccountName;
    	}
    }
  11. NoteController.java: Basic CRUD operations on the JPA model “Note” mentioned below.
  12. @RestController
    @RequestMapping
    public class NoteController {
    
        @Autowired
        NoteRepository noteRepository;
    
        @GetMapping("/notes")
        public List<Note> getAllNotes() {
            return noteRepository.findAll();
        }
    
        @GetMapping("/notes/{id}")
        public ResponseEntity<Note> getNoteById(@PathVariable(value = "id") Long noteId) {
            Note note = noteRepository.findOne(noteId);
            if(note == null) {
                return ResponseEntity.notFound().build();
            }
            return ResponseEntity.ok().body(note);
        }
    
        @PostMapping("/notes")
        public Note createNote(@Valid @RequestBody Note note) {
            return noteRepository.save(note);
        }
    
        @PutMapping("/notes/{id}")
        public ResponseEntity<Note> updateNote(@PathVariable(value = "id") Long noteId,
                                               @Valid @RequestBody Note noteDetails) {
            Note note = noteRepository.findOne(noteId);
            if(note == null) {
                return ResponseEntity.notFound().build();
            }
            note.setTitle(noteDetails.getTitle());
            note.setContent(noteDetails.getContent());
    
            Note updatedNote = noteRepository.save(note);
            return ResponseEntity.ok(updatedNote);
        }
    
        @DeleteMapping("/notes/{id}")
        public ResponseEntity<Note> deleteNote(@PathVariable(value = "id") Long noteId) {
            Note note = noteRepository.findOne(noteId);
            if(note == null) {
                return ResponseEntity.notFound().build();
            }
    
            noteRepository.delete(note);
            return ResponseEntity.ok().build();
        }
    }
  13. Note.java: Sample JPA entity used in this application.
  14. NoteRepository.java: Repository class for Note JPA entity.

 

TESTING

Test the sample application whether it is multi-tenant or not by accessing the end-point “/notes”

 

Here are the steps that needs to be followed for testing:

 

  1. Start the application and copy the application URL for the provider application and append /notes to the end of the URL.
  2. Open postman and add request headers as shown in below screenshot:

    In the Authentication tab, choose Basic Authentication and enter the required credentials. Click update request after entering the credentials.
  3. In the body section, add a sample JSON as shown in below screenshot.

     

  1. Click on SEND, and check the response if it is 201.
  2. Perform the above steps 2 to 4 using the subscriber application URL (this you can get by going to the subscriber account and click on subscriptions and go to the respective application), just make sure you pass different title and content in the JSON so that you can uniquely distinguish.
  3. In our code, we have already added spring o-data library package marker in main class, so we can use odata for fetching the database entries.
  4. Append /odata/Notes to provider URL (example shown below) and check whether right title and content is shown which you posted for the provider URL. You will observe that only the content of the provider database is shown.

Sample Provider URL: https://aaxxx9xxxxx4.sap.hana.ondemand.com/demo-0.0.1-                                                                  SNAPSHOT/odata/Notes

  1. Repeat the above step for subscription account URL, you will observe that content of subscription account is only shown. You can test this with as many number of customer accounts on the HANA MDC system.

 

Limitation of this multi-tenancy model:

  1. The design of your application needs to be in such a way that there are as such no static blocks which can create an issue by storing the data across multiple DB’s.
  2. The spring components like Scheduler and all expects a fixed data source and hence cannot be used with this multi-tenancy model.
  3. This concept works only if the schema (tables and all) is created in all the accounts, subscriber accounts as well as provider account, so you need to follow the model of DU deployment or any other way so that schema is initialized in all the tenant databases before running multi-tenant application.

 

Conclusion:

When to use Tenant Discriminator Column vs HANA MDC to achieve Multi-Tenancy

 

As explained in the below blog:

https://blogs.sap.com/2016/12/19/developing-multi-tenant-applications-on-hcp-persistence-multitenancy-part-4/

Multi-tenancy can be achieved using tenant discriminator column and here the entire database is shared among all customers. This is currently the most popularly used model for provider based applications.

Below are few points which guides you the approach for developing provider based application:

  1. There can be demand from the customers of a country where they might feel their data is insecure with the database being shared.In such cases HANA MDC is an option to be considered since it provides highest level of data isolation and security to customers data.
  1. Sometimes there might be infrastructure constraints which will limit the number of tenant databases, in that case tenant discriminator column model could be used.
  1. It also depends on the application architecture since there might be some legacy components which may not work with MDC based approach. Hence, tenant discriminator model can be used.

 

Be the first to leave a comment
You must be Logged on to comment or reply to a post.