Skip to Content
Technical Articles
Author's profile photo Prabhanjan Kumar

Multitenant application using Cloud Application Programming Model (CAP)

This example demonstrates how to build multitenant application using Cloud Application Programming Model Java SDK and mtx-sidecar node module.

Overview:

This sample application contains

  • UI module containing a public welcome page which contains link to fetch data from its own tenant database.
  • backend module which contains CDS service exposing OData CRUD APIs on Books entity
  • Database (db) module which contains schema.cds to create books table
  • Node.js based mtx-sidecar module used to handle tenant provisioning

What is Multitenancy?

SAP BTP provides a multitenant functionality that allows application providers to own, deploy, and operate tenant-aware applications for multiple consumers, with reduced costs.

With tenant-aware applications, you can:

  1. Separate data securely for each tenant
  2. Save resources by sharing them among tenants
  3. Update applications efficiently in a single step

Configuring the Approuter, required services and mtx-sidecar module:

To enable multitenancy on the SAP BTP, we need to deploy tenant aware approuter, mtx-sidecar module and configure below three services.

Only when these services are bound to your application, the multitenancy feature is turned on.

  1. XSUAA
  2. Service Manager
  3. SaaS Provisioning service (saas-registry)

Approuter module:

You deploy the approuter application as a Cloud Foundry application and as a logical part of the multitenant application. Then you configure approuter application as an external access point of the application.

Each multitenant application has to deploy its own application router and the application router handles requests of all tenants to the application.

The application router must determine the tenant-specific subdomain. This determination is done by using a regular expression defined in the environment variable TENANT_HOST_PATTERN. The application router then forwards the authentication request to the tenant User Account and Authentication (UAA) service and the related identity zone.

If you have multiple routes to the same application, for example:

tenant1.<application domain> and tenant2.<application domain>

The TENANT_HOST_PATTERN could be:

TENANT_HOST_PATTERN: "^(.*)-<application domain>"

 

 

XS UAA:

Bind your multitenant application and the approuter application to the SAP Authorization and Trust Management service (technical name: xsuaa) instance, which acts as an OAuth 2.0 client to your application.

In multi-tenant environments, tenants subscribe to and consume applications, which are registered as clients at the XS UAA. XS UAA creates a new OAuth2 client per application for each tenant. The shared tenant mode is mandatory for an application router configured for multi-tenancy applications. Also, a special configuration of an XS UAA service instance is required to enable authorization between the SaaS Provisioning service, Cloud Application Programming Model Java application, and MTX sidecar.

The service can be configured in the mta.yaml by adding an xsuaa resource as follows:

-  name: <xsuaa instance name>
   type: com.sap.xs.uaa
   parameters:
      service-plan: broker
      path: ./security.json
      config:
         xsappname: <appname>

Choose a value for property xsappname that is unique globally.

An example security.json file looks like this:

{
    "xsappname": <appname>,
    "tenant-mode": "shared",
    "scopes": [
        {
            "name": "$XSAPPNAME.mtcallback",
            "description": "Multi Tenancy Callback Access",
            "grant-as-authority-to-apps": [
                "$XSAPPNAME(application, sap-provisioning, tenant-onboarding)"
            ]
        },
        {
            "name": "$XSAPPNAME.mtdeployment",
            "description": "Scope to trigger a re-deployment of the database artifacts"
        }
    ],
    "authorities": [
        "$XSAPPNAME.mtdeployment"
    ]
}

The mtcallback scope is required by the onboarding process. The mtdeployment scope is required to redeploy database artifacts at runtime.

Service Manager:

A service-manager instance is required that the Cloud Application Programming Model Java SDK can create database containers per tenant at application runtime. It doesn’t require special parameters and can be added as a resource in mta.yaml as follows:

-  name: service-manager
   type: org.cloudfoundry.managed-service
   parameters:
      service: service-manager
      service-plan: container

SaaS Provisioning Service (saas-registry):

A saas-registry service instance is required to make your application known to the SAP BTP Provisioning service and to register the endpoints that should be called when tenants are added or removed. The service can be configured as a resource in mta.yaml as follows

- name: saas-registry
  type: org.cloudfoundry.managed-service
  parameters:
    service: saas-registry
    service-plan: application
    config:
        appName: <app display name> # this is the text on the tile
        xsappname: <appname> # this is the value from xsuaa.parameters.config.xsappname
        appUrls:
          getDependencies: ~{srv/url}/mt/v1.0/subscriptions/dependencies
          onSubscription: ~{srv/url}/mt/v1.0/subscriptions/tenants/{tenantId}
  requires:
    - name: srv
  • appName: Choose an appropriate application display name.
  • xsappname: Use the value for xsappname you configured at your UAA service instance.
  • appUrls: Configure the callback URLs used by the SaaS Provisioning service to get the dependencies of the application and to trigger a subscription. In the above example, the property ~{srv/url} that is provided by the srv module is used.

mtx-sidecar module:

Cloud Application Programming Model provides the npm module for Node.js published as @sap/cds-mtx on npmjs.com. It provides APIs for implementing SaaS applications on SAP BTP. Java applications need to run and maintain the cds-mtx module as a sidecar application. Multitenant Cloud Application Programming Model Java applications automatically expose the tenant provisioning APIs.

provisioning: implements the subscription callback API as required by SAP BTP. If a tenant is subscribing to the SaaS application, the onboarding request is handled. cds-mtx is contacting the SAP HANA Service Manager service to create a new HDI container for the tenant. Then, database artifacts get deployed into this HDI container. In addition, the unsubscribe operation and the “get dependencies” operations are supported.

This section describes how to use the cds-mtx Node.js module and add the MTX sidecar microservice to the mta.yaml file.

In a dedicated project subfolder named mtx-sidecar, create a Node.js start script in a file named server.js to bootstrap the cds-mtx library:

const app = require('express')();
const cds = require('@sap/cds');

const main = async () => {
	await cds.connect.to('db');
	const PORT = process.env.PORT || 4004;
	const scope = process.env.CDS_MULTITENANCY_SECURITY_SUBSCRIPTIONSCOPE || 'mtcallback';
	await cds.mtx.in(app);

	const provisioning = await cds.connect.to('ProvisioningService');
	provisioning.before(['UPDATE', 'DELETE', 'READ'], 'tenant', async (req) => {
		// Check for the scope of the SaaS Provisioning Service
		if (!req.user.is(scope)) {
			// Reject request
			const e = new Error('Forbidden');
			e.code = 403;
			return req.reject(e);
		}
	});

	app.listen(PORT);
}

main();

To define the dependencies and start command, also create a file package.json like this:

{
    "name": "deploy",
    "dependencies": {
        "@sap/cds": "^5.0.4",
        "@sap/cds-mtx": "^1.2.0",
        "@sap/hdi-deploy": "^4.0.4",
        "@sap/instance-manager": "^2.2.0",
        "@sap/xssec": "^3",
        "@sap/hana-client": "^2.8.16",
        "express": "^4.17.1",
        "passport": "^0.4.1"
    },
    "scripts": {
        "start": "node server.js"
    }
}

Because the MTX sidecar will build the CDS model, you need to configure the build by means of two .cdsrc.json files:

The first .cdsrc.json file goes into the root folder of your project and specifies from which location the CDS files should be collected. The following example demonstrates this:

{
    "build": {
        "target": ".",
        "tasks": [
            {
                "for": "java-cf"
            },
            {
                "for": "mtx",
                "src": ".",
                "dest": "mtx-sidecar"
            },
            {
                "for": "hana"
            }
        ]
    },
    "hana": {
        "deploy-format": "hdbtable"
    },
    "sql": {
        "dialect": "plain"
    }
}

The second .cdsrc.json file goes into the mtx-sidecar directory. This could look, for example, like:

{
    "hana": {
        "deploy-format": "hdbtable"
    },
    "build": {
        "tasks": [
            {
                "for": "hana",
                "src": "db",
                "options": {
                    "model": [
                        "db",
                        "srv",
                        "app"
                    ]
                }
            },
            {
                "for": "java-cf",
                "src": "srv",
                "options": {
                    "model": [
                        "db",
                        "srv",
                        "app"
                    ]
                }
            }
        ]
    },
    "odata": {
        "version": "v4"
    },
    "requires": {
        "db": {
            "kind": "hana",
            "multiTenant": true,
            "vcap": {
				"label": "service-manager"
            }
        },
        "uaa": {
            "kind": "xsuaa"
        }
    }
}

Now, add the mtx-sidecar module to your mta.yaml file:

# --------------------- SIDECAR MODULE -----------------------
-  name: mtx-sidecar
# ------------------------------------------------------------
   type: nodejs
   path: mtx-sidecar
   parameters:
      memory: 256M
      disk-quota: 512M
   requires:
   -  name: xsuaa
   -  name: service-manager
   provides:
   -  name: sidecar
      properties:
         url: ${default-url}

The mtx-sidecar module requires the XS UAA and Service Manager services. Also you need to provide its URL to be able to configure the URL in the service module

Wiring It Up:

To bind the previously mentioned services and the MTX sidecar to your Cloud Application Programming Model Java application, you could use the following example of the srv module in the mta.yaml file:

# --------------------- SERVER MODULE ------------------------
-  name: multitenantapp-srv
# ------------------------------------------------------------
   type: java
   path: srv
   properties:
      SPRING_PROFILES_ACTIVE: cloud
      CDS_MULTITENANCY_APPUI_TENANTSEPARATOR: "-"
   parameters:
      memory: 1024M
      disk-quota: 256M
      buildpack: sap_java_buildpack
   build-parameters:
      builder: custom
      commands:
        - mvn clean package -DskipTests=true
      build-result: target/*-exec.jar
   requires:
   -  name: service-manager
   -  name: xsuaa
   -  name: saas-registry
   -  name: sidecar
      properties:
         CDS_MULTITENANCY_SIDECAR_URL: ~{url}
   -  name: app
      properties:
         CDS_MULTITENANCY_APPUI_URL: ~{url}
   provides:
   -  name: srv
      properties:
         url: '${default-url}'

Deploy to SAP Business Technology Platform:

Please follow below steps to deploy to SAP BTP (Cloud Foundry) Trial landscape.

  • Create an SAP HANA Cloud Instance in your SAP Business Technology Platform space.

Please follow below screenshots to create an instance and in step 3, please select allow all IP addresses as shown in the screenshot. You can select all default values and click on create. This will take few minutes.

Create SAP HANA Cloud Instance:

Screenshot 1:

Create%20HANA%20Cloud%20Instance

Create HANA Cloud Instance

Screenshot 2:

AllowAllIPAddresses

AllowAllIPAddresses

Note: once the SAP HANA Cloud instance is created, please copy the instance id and replace the value in SubscriptionHandler.java as follows.

@Before(event = MtSubscriptionService.EVENT_SUBSCRIBE)
public void beforeSubscription(MtSubscribeEventContext context) {
	context.setInstanceCreationOptions(
	new InstanceCreationOptions().withProvisioningParameters(
		Collections.singletonMap("database_id", <<INSTANCE ID>>)));
}
  • Run mbt build
  • Run cf login
  • Run cf deploy mta_archives/multitenantapp_1.0.0-SNAPSHOT.mtar

Sample Demo:

Once the deployment is successful, you should see all the 4 apps in started status as below.

Apps%20Deployed

Apps Deployed

Create a new SubAccount to onboard a new tenant as shown below.

SubAccount%20creation

SubAccount creation

Subscribe to the Multi Tenant App as below.

Subscribe%20to%20application

Subscribe to application

Subscription%20Success

Subscription Success

Go to application and you will see route does not exists error as below.

Go%20To%20Application

Go To Application

Route%20does%20not%20exists

Route does not exists

Add tenant url as a new application route and map tenant URL to AppRouter URL as shown below.

Add%20Route

Add Route

Map%20Route

Map Route

Access the application using the newly created tenant URL and this should display a welcome page as below. Click on Get My Tenant Data link and should authenticate the user against SAP Default IDP (enabled by default) and should show all Book entries from the tenant database as below.

Welcome

Welcome

Books%20Listing%20no%20data

Books Listing no data

Add a new book entry to tenant database and check the same using Get My Tenant Data link as shown below.

First we need to request for CSRF token.

Get%20CSRF%20Token

Get CSRF Token

Post a book entry.

Post%20book%20entry

Post book entry

Refresh the page and check if data is retrieved.

Books%20Listing%20with%20data

Books Listing with data

 

Summary:

In this blog, I have tried to demonstrate how to build multitenant application using Cloud Application Programming Model Java SDK and cds-mtx node module. You can find working example at https://github.tools.sap/I348557/CAPJAVAMultiTenantApp.

Please note that, I have developed and tested this example using SAP Business Application Studio and SAP BTP Trial landscape.

Links and Further Reading:

Capire >> Multitenancy

SAP Business Technology Platform >> Development >> Development in the Cloud Foundry Environment >> Developing Applications and Services >> Developing Multitenant Applications in the Cloud Foundry Environment

Cloud Application Programming Model Samples for Java

SAP HANA Academy >> SAP BTP Multitenant Business Applications

 

How to obtain support/help:

Please provide your feedback, thoughts in the comments section.

 

Thanks,

Prabhanjan

 

 

 

Assigned tags

      2 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Silas Rocha
      Silas Rocha

      Do you have some example on how to do that using nodejs ?

      Author's profile photo Lucia Wu
      Lucia Wu

      Hi Kumar, WHEN I download your code, and deploy to my btp, but error when I subscribe it.

      I just update

       

      two palce:
      xsappname: multitenantapp-dev
      one palce:
      appName: multitenantapp-dev