Technical Articles
Developing Multitenant Applications on SAP Cloud Platform, Cloud Foundry environment
Software-as-a-Service (SaaS) applications are becoming more and more relevant now with the ease of delivery to customers. It is impossible to be successful in SaaS without utilizing the capabilities of multitenancy. There is already a blog that explains how to develop a multitenant application in the Neo environment on SAP Cloud Platform.
In this blog, I’ll show you the concept of multitenancy in the Cloud Foundry environment together with a sample multitenant business application that we’ve developed to showcase the consumption of relevant services in the Cloud Foundry environment on SAP Cloud Platform.
As a prerequisite to this blog, I strongly recommend that you read about the changes to the domain model in SAP Cloud Platform in this blog. It is also assumed that you are already aware of the provider vs. consumer (tenant) concepts of multitenancy.
Let’s start off by looking at the components that are needed for a multitenant business application to run in the Cloud Foundry environment in SAP Cloud Platform.
The application owner (the provider of the multitenant application) owns the global account and the subaccount where the application is hosted in SAP Cloud Platform. The application providers can manage subscriptions for their customers in order to use the multitenant features of the SAP Cloud Platform.
Some of the aspects of multitenancy, such as being able to subscribe to applications hosted in other global account and the monetization of the provider applications might be available in the future releases.
The application uses the tenant-aware approuter service, which is the single point-of-entry for the application running on Cloud Foundry environment. The xsuaa service is used for authentication at runtime. The application is registered with SaaS Provisioning (saas-registry) service, which enables the subscription lifecycle events of the application with the consumer tenants.
Which coding approach should you follow?
Multitenancy at the persistence level can be approached in various ways, each with its pros and cons:
- Column Discriminator – data is stored in the same schema of the same database and tenant details are maintained in a column of the table.
- This is the most cost efficient, but separation is very weak and tenant-specific configurations are hard to implement.
- Tenant-specific backup and restore is not possible.
- Schema Separation – data is stored in a separate schema (same database) per tenant.
- Cost efficiency and data separation are balanced.
- Extension of the schema per tenant is possible.
- Requires additional logic for backup and restore to avoid overwriting the entire database in case of schema corruption.
- Database Instances – every tenant has its own database instance.
- Provides the highest level of data isolation as the instances are physically separated.
- This entails the highest cost as additional server instances are needed to store each database.
- Backup and restore is possible for each tenant.
- Underutilization is a possibility.
You can find more details about these approaches in this blog.
From a cost and isolation perspective, the recommended approach is schema separation.
SAP Cloud Platform also provides the automated onboarding/offboarding capabilities for the application provider. This is done through a couple of API callbacks:
- getDependencies: Provides dependencies to multitenant reuse services.
- onSubscription: Provides the logic of setting up a tenant in the application (subscription). The same callback is also used to unsubscribe a tenant. This callback must be implemented to return the application URL that suits the tenant host pattern.
The tenant host pattern is used in a manifest.yml file to identify the tenant that is accessing the application.
TENANT_HOST_PATTERN: <<tenant>>-<<appname>>.<<domain>>
Let’s look at some coding snapshots
With this blog, we provide a sample application as a reference point to get this fully implemented. This application is about a Product master. The application stores the details of products that are specific to the tenant (customer). The code snapshots that follow based on column discriminator multitenancy.
The sample application contains the following structures and entities:
- applicationBackend
- applicationUIModule + appRouter
- mtConfig (callback implementation)
- xs-security.json (XSUAA configuration)
- yml (for Multi-Target Application builds)
The xs-security.json file describes the scope and the tenant-mode:
{
"xsappname": "inventorymanagementapp",
"tenant-mode": "shared",
"description": "Security profile of inventory management app",
"scopes": [{
"name": "$XSAPPNAME.Callback",
"description": "With this scope set, the callbacks for tenant onboarding, offboarding and getDependencies can be called.",
"grant-as-authority-to-apps": [
"$XSAPPNAME(application,sap-provisioning,tenant-onboarding)"
]
}]
}
- The xsappname must be unique per the XSUAA instance.
- The tenant-mode should be set to ‘shared’ and will require that the TENANT_HOST_PATTERN is declared in the yml file as mentioned earlier.
- For single-tenant applications, the tenant-mode must be declared as ‘dedicated’.
- The scope mentioned in the above code snapshot must be granted to the tenant-onboarding service as mentioned in the description of the scope. This is only required for automated onboarding of tenants.
To consume a backing service such as SAP HANA, PostgreSQL and so on, the service instance of the backing service must be bound to the application. You can bind the service instances to an application either from the SAP Cloud Platform cockpit or through the CF CLI as mentioned in the
In the UI model of an SAP UI5 application, you must ensure to maintain the approuter dependency in the package.json.
"dependencies": {
"@sap/approuter": "2.7.1"
},
"scripts": {
"start": "node node_modules/@sap/approuter/approuter.js"
}
....
}
The configuration for the approuter (xs-app.json) must contain the necessary routes to the UI application and the backend application.
{
"welcomeFile": "/inventorymanagementui/index.html",
"authenticationMethod": "route",
"logout": {
"logoutEndpoint": "/logout"
},
"routes": [{
"source": "^/inventorymanagementui/(.*)$",
"target": "$1",
"localDir": "webapp"
}, {
"source": "^/inventorymanagementbackend/(.*)$",
"target": "$1",
"csrfProtection": true,
"authenticationType": "xsuaa",
"destination": "inventorymanagementbackend_api"
}]
}
The backend application uses the sapxsenv and xssec libraries to parse the environment variables and the JSON Web Token (JWT) strategy, respectively.
var xsenv = require('@sap/xsenv');
var JWTStrategy = require('@sap/xssec').JWTStrategy;
var services = xsenv.getServices({ uaa:'uaa_inventorymanagementapp' });
passport.use(new JWTStrategy(services.uaa));
app.use(passport.initialize());
app.use(passport.authenticate('JWT', { session: false }))
Let’s now look at the implementation of the discriminator column as mentioned in the previous sections.
For a simple column-discriminator persistence, the query to retrieve the tenant-specific data would look like this:
var selectAllProductsQuery = "SELECT * FROM products WHERE \"tenant_id\" = " + tenant_id;
For inserting values, the statement would look like this:
"INSERT INTO products (\"product_name\",\"product_description\",\"supplier_name\",\"price\",\"available\", \"quantity\",\"tenant_id\")" +
"VALUES($1,$2,$3,$4,$5,$6,$7);";
For detailed documentation about these steps, refer to Developing Multitenant Business Applications in the Cloud Foundry Environment on SAP Help Portal.
In subsequent blogs that we’ll be posting, you’ll learn about the recommended schema-based multitenant persistency on an SAP HANA database.
References and sequels:
Sample code – Developing a SaaS Multitenant Business Application on SAP Cloud Platform Cloud Foundry Environment
Sample Code 2 – Multitenancy on HANA
Blog – Architecture overview
Very useful Blog Hariprasauth.
Thanks for sharing. Looking forward for next Blogs.
is the sample code available in github?
Best Regards,
Venu
Hi Venu,
Happy that you liked the blog! Keep watching this space for the sample code. We are just in the process of publishing the code along with the guide!
Thanks for the support..
Cheers,
Updated with sample code!
Thank you for a very detailed description, Hariprasauth,
would that be possible to provide a link to your sample application in the Github?
Thank you and best regards, Kirill.
Thank you reading the blog, Kirill. We are in the process of releasing the code sample. Keep watching this space for more information.
Regards, Hari
Updated with the code!
Hi Hari
I've tried to bring your example project from Git to my CF Trial account, but the deploy to CF failed.
(see the following error msg). Have I done something wrong or isn't it possible to run the example on the CP Trial?
Thnx and BR Hans-Joachim
Preparing to deploy /mta_archives/inventorymanagementapp/inventorymanagementapp_1.0.0.mtar
Checking if there are conflicting processes
Found 1 conflicting processes
Aborting process 73314494
Uploading archive
Starting deployment
Validating parameters...
Processing MTA archive...
Processing MTA extension descriptors...
Detecting MTA major schema version...
MTA schema version: 3.1.0
Detecting deploy target...
Detected deploy target "Pxxxxxxxxtrial_trial dev"
Validating and merging descriptors...
Detecting deployed MTA...
No deployed MTA detected
Collecting system parameters...
New MTA version: 1.0.0
Error resolving merged descriptor properties and parameters: Unable to resolve "inventorymanagementui#inventorymanagementbackend_api#url"
Unexpected error: Unable to resolve "inventorymanagementui#inventorymanagementbackend_api#url"
Exception occured during execution: "Unable to resolve "inventorymanagementui#inventorymanagementbackend_api#url""
Job failed
Hi Hariprasauth,
Thanks for the blog very much. What if the business application has some other third party services bound e.g. a Workflow Service, Destination or Connectivity etc.? After subscription, does the customer have to create these service instances in their Subaccount or not? If yes, how does our business application know these service instances under customer's Subaccount and access them? If no, these service instance may have their own data storage which is not controlled by us then how to do the data isolation for different customers there? Thanks very much.
Best regards,
Eric Jiang
Hi Eric,
Thanks for following the blog! The application provider can architect the code in a way to read the configurations from the consumer side. This way, the customers/consumers can simply configure their respective URLs.
Regards,
Hari
Hi Hari,
Great blog,Eagerly awaiting on the next blog on recommended schema-based multi tenant persistence on an SAP HANA database using Dynamic HDI Mapping .It would be great if you can also have a demonstration with Spring Boot as back end application.
Thanks,
Siddharth
Hi Siddharth,
May be this code helps.
Regards,
Hari
Thanks Hari for your efforts and help,I noticed a very strange thing ,the sample inventory management app which is a available on Git hub ,i deployed it previously and did the subscription and unsubscribe it before but now Unsubscribe feature is not working ,it just stay as subscribed, is there any change in API ,call back just return status 200 in your nodejs application code ,what is going wrong ,please suggest.
Hi Hari,
Thanks for your sharing. Any updates on the next blog about schema-based multi tenant?
Best Regards
Clark Huang
Hi Clark,
It should be out soon.
Regards,
Hari
Thanks for the blog! I'd also be very interested in some multitenant Java samples that use HANA DB backend w/ schema per tenant. Is there such an example or documentation on how to accomplish this?
Hi Josh,
Hands-on video tutorials are now available that cover the multitenant topic including HANA-based schema separation - see this blog for details:
Hands-on Video Tutorials for Developing Multitenant Business Applications
Regards,
Philip
Hi Hariprasauth,
Great blog!
I was also trying to develop a multi tenant app and have followed the steps mentioned here. But i get the following error: "Authorization request error- No client with requested id: ******* ".
Could you please help.
Thanks and Regards,
Treasa
Hi Hariprasauth,
Is the UAA has to be set as "tenantmode": "shared"? I found that the authentication process will be link to different IdP if I set it as "shared".
Best regards,
Terry
Hi Terry
I found the same issue with the idp. If the tenant-mode set to dedicated, it'll redirect to my IAS service for login. But with tenant-mode set to shared, it'll use the default SAP HANA XS Advanced login. Have you found any solutions for this?
Cheers
Andrew
Hi Hari / Terry / Andrew,
Facing similar issue (redirecting to default SAP HANA XS Advanced login page) when using tenant-mode as "shared" in xs-security.json. Is there any config issue that should be looked into?
Thanks
Regards,
Suraj Acharya
Hi,
I do have the same problem and get redirected to the XS Advanced Login, where my user won't be recognized. Did you find any solution for this?
Kind Regards,
Mike