Introduction to multi-tenant concepts
Overview
The code discussed in this guide can be found in the samples repository. The idea behind this tutorial is to explain the main concepts of multi-tenant applications and how to implement them on SAP BTP. The code in the example is not a copy-paste solution for productive use but a didactic sample. You need to adjust things like application names or routes for your use case.
The term "multi-tenant application" is more or less a synonym for a software as a service (SaaS) offering. The idea behind this architecture is that the consumers share the application resources, so they are used more effectively. On SAP BTP, organizations are modeled by subaccounts, and multi-tenant applications are subscribed per subaccount. These organizations are potentially different companies or strongly separated parts of one company. In any case, each organization has its subaccount on SAP BTP and subscribes to a multi-tenant application.
Some vocabulary is necessary to understand the next chapters of this tutorial:
- provider account: The SAP BTP account which hosts the actual application. This account is under the control of the application developer.
- subscriber accounts: The accounts using the application. These accounts are controlled by the consumer.
- tenant-aware service: A service which separates the data of different
subscriber accounts
rigorously. If you build a multi-tenant application, all services handling account specific data need to be tenant-aware services.
This tutorial is not a complete guide on the multi-tenancy topic in SAP. It rather covers only points where the SAP Cloud SDK team saw the need of a more detailed guide with samples. Have a look at the following guides and tutorials offering detailed information:
- The SAP-Hana-Academy contains a complete SaaS example for CF and K8s.
- The multi-tenant application documentation contains all technical details for development.
- The approuter documentation explains the approuter concepts.
- There are many blogs talking about multi-tenant application development: For example, the blog from Sandeep provides a good overview or the blog from Raja is also a good starting point.
- This tutorial skips important topics like Custom domains or Role and authorization concepts.
Note that the subscriber and provider account need to be in the same global account. In case you want to offer a service across global accounts you may follow the service broker approach which has other limitations.
Prerequisites
To execute this tutorial, you need:
- Two CF subaccounts in the same global account to represent provider and subscriber accounts.
- The provider account needs some quota:
- To host two applications (sample application and approuter)
- To create a service instance for the destination and XSUAA service
- You need a basic understanding of SAP BTP and the Cloud Foundry CLI.
The Application
The application is a minimal example which contains only one endpoint containing business logic. This endpoint will call the destination service using the SAP Cloud SDK. Since the destination service is tenant aware, it can be used to illustrate service usage within your multi-tenant application. You can find the application code in the multi-tenant-app folder. The relevant application logic and configuration is located in the following three files:
- In the
application.ts
file, the different endpoints are defined. For now, only the/service
endpoint is relevant, which represents our multi-tenant service. - In the
manifest.yml
file, the route to the application is given and the used services are defined. - In the
service-endpoint.ts
a tenant-aware service (destination service) is called and tenant information is collected. The endpoint represents the service offering for the subscriber accounts.
- application.ts
- manifest.yml
- service-endpoint.ts
Deploy the Application
Before you can deploy the application, you need to create a service instance for the destination and XSUAA service in your account.
There is an xs-sercurity.json
file in the service-config
folder to create the XSUAA instance.
Align the name of your service instances with the ones in the manifest.yml
.
Also, adjust the route to use the region of your CF, e.g., cfapps.us10.hana.ondemand.com
, and the route path to make it unique in the region.
Now, log into CF using the CLI cf login
and enter the account information of the provider account.
Navigate to the multi-tenant-app
folder and execute cf push
to deploy the application.
Call the Service
In our example, the service is reachable via (for you the URL will be different depending on landscape):
GET https://multi-tenant-app.cfapps.YOUR_REGION.hana.ondemand.com/service
The implementation of service-endpoint.ts
does the following:
- Extracts a JSON web token (JWT) from the request.
- Fetches a destination with the name
myDestination
using the destination service.
Since there is no destination with that name, the service will return 404
.
Create a destination with the name in your provider account and also enter some description for that destination. After the destination is created, the service should return:
No jwt given in request. Provider tenant used. The destination description is: Provider Destination Description
The Approuter
The response shows that there is no JWT attached to the request. This task is done by the application router, the XSUAA, and the identity provider (IdP). Just think of the approuter as an application taking requests and initiating the authorization flow with the XSUAA and IdP. Once the user enters their credentials, the request is sent to the target with the JWT issued for the user and account.
More information on the approuter topic can be found in this guide.
In a productive case, the approuter may redirect requests to multiple applications. In our simple example, there is just one route.
The approuter does not require code, only configuration. You can find all files in the approuter folder.
- The
manifest.yml
file contains the config for the approuter - The
xs-app.json
file contains the config for the route resolution.
- manifest.yml
- xs-app.json
Deploy the Approuter
Please adjust the route property in the manifest.yml
file.
Replace the placeholders for subdomain and region.
Log into the provider account using cf login
and call cf push
from the approuter
directory.
This deploys the approuter.
Once the approuter is deployed, you will see it running as a separate application next to your multi-tenant-app
.
When you open the approuter application you see one route created by the manifest:
GET https://route-prefix-YOUR_SUBDOMAIN.cfapps.YOUR_REGION.hana.ondemand.com/
When you follow this route, you will get redirected to the welcomeFile
defined in the xs-app.json
.
The index.html
is located in the application.
How did the routing work:
- In our simple scenario, the
xs-app.json
file defines only one route consisting of asource
,target
, anddestination
. Thesource
is a regex and the target defines which capturing group is used in the destination. In our examplehttps://route-prefix-YOUR_SUBDOMAIN.cfapps.YOUR_REGION.hana.ondemand.com/SOME_VALUE
will lead toSOME_VALUE
as the capturing group andSOME_VALUE
is attached to the destination. There are many more options to the routing config explained here. - The
manifest.yml
defines the available destinations for the approuter. The destinationmulti-tenant-app
points to the URL of our application. Thereforehttps://route-prefix-YOUR_SUBDOMAIN.cfapps.YOUR_REGION.hana.ondemand.com/SOME_VALUE
goes tohttps://multi-tenant-app.cfapps.YOUR_REGION.hana.ondemand.com/SOME_VALUE
Call the Service via the Approuter
The reason for introducing the approuter was the missing JWT in the request. If you call the service via the approuter:
GET https://route-prefix-YOUR_SUBDOMAIN.cfapps.YOUR_REGION.hana.ondemand.com/service
you will see a response like:
You are on tenant: a89ea924-d9c2-4gaf-84fb-3ffcff123456. The destination description is: Provider Destination Description
which shows that the request contains a JWT issued for the provider account.
The Subscription
Up to now, you called the application via the provider account. In this chapter you learn how to call the service from a different account.
The first thing to do is to create an instance of the SaaS provisioning service
in your provider account.
You can find the saas-registry-config.json
in the samples repository.
This makes the service subscribable from other accounts.
You need to adjust the providerTenantId
to contain your ID and the appUrls
to match your region and application URL.
Within the saas-registry-config.json
two URLs are mentioned: the getDependencies
and onSubscription
.
- saas-registry-config.json
- dependencies-endpoint.ts
- subscription-endpoint.ts
These two endpoints are the entry point for the SAP BTP platform to:
- Create and delete a subscription to the application
- Obtain the services used by the application
In our example the application uses the destination service.
The application has a binding to a service instance, so it is clear that the application may call the service.
However, the subscriber account does not know anything about the internal details of the application.
Therefore, the /dependencies
endpoint provides the information that the destination service may be used also from the subscriber account.
Remember to add all SAP BTP services used by your application in the response of the dependencies endpoint.
If you do not do that, you retrieve a 403
error from the XSUAA when you request a service token on behalf of a subscriber account.
Creating a Subscription
After you have created an instance of the SaaS provisioning service in the provider account, you can create a subscription via the user interface.
A subscription is a route to the provider application, including the unique subdomain of the subscriber account.
The TENANT_HOST_PATTERN
in the manifest.yml
of the approuter defines how to extract the subscriber account from the URL.
A route like:
GET https://route-prefix-YOUR_SUBDOMAIN.cfapps.YOUR_REGION.hana.ondemand.com/service
would mean that the subscriber account is YOUR_SUBDOMAIN
.
To automate the onboarding of accounts, the script in subscription-endpoint.ts
does the following:
- It creates a route
- Binds the created route to the approuter.
- Returns the route URL so that it can appear in the subscriber account.
The creation of the route uses the CF API.
Unfortunately, there is no out-of-the-box access of this API when you are in the context of an application.
The code assumes a destination with the name cf-api
in the sample implementation which contains the access data for the CF API:
property | value |
---|---|
name | cf-api |
authentication | OAuth2Password |
user | a user with permission to the provider account |
password | password of this user |
client id | cf |
client secret | empty string |
token service URL | https://login.cf.YOUR_REGION.hana.ondemand.com/oauth/token |
URL | https://api.cf.YOUR_REGION.hana.ondemand.com |
You have to adjust the URL and token service URL to for your region e.g. https://api.cf.us10.hana.ondemand.com
.
Once the destination is present, you can subscribe to the application and routes are created automatically.
Log into your second SAP BTP account.
Go to Service->Instances and Subscriptions and create a subscription to the multi-tenant-app
.
Once the application is subscribed, you can have a look at the approuter in the provider account.
You should see a second route with the subdomain of the subscriber.
If you call the new route:
GET https://route-prefix-someSubscriberDomain.cfapps.YOUR_REGION.hana.ondemand.com/service
you will see a response with the tenant ID from the subscriber account:
You are on tenant: a89ea924-d9c2-4gaf-84fb-3ffcff7891011. The destination description is: Provider Destination Description.
The approuter has extracted the subscriber subdomain from the URL and issued a token for this account. As an application developer, you can use the token to determine the account which calls your code.
Removing a Subscription
If the consumer deletes the subscription, the SAP BTP will invoke the DELETE
method on the subscription-endpoint.
The code will remove the route from the approuter and make the application unreachable for that consumer.
The details of the implementation can be found in the subscription-endpoint.ts
of the sample application.
Real World View
The presented example is totally artificial. This chapter elaborate a bit on what an actual multi-tenant application would look like and how the SAP Cloud SDK helps you. Different consumer are divided by their unique application URL including their subdomain. However, up to now, nothing subscriber-specific is happening in the implementation.
To get an idea create a destination in the subscriber account with the same name myDestination
with a different description e.g. Subscriber Destination
.
A call to the same /service
endpoint will lead now :
You are on tenant: a89ea924-d9c2-4gaf-84fb-3ffcff7891011. The destination description is: Subscriber Destination.
The destination of the subscriber account is used at runtime, because the call in the service-endpoint.ts
uses the selection strategy subscriberFirst
.
You can change this by using different selection strategies.
This enables consumers to maintain their custom destination used within a multi-tenant application.
The destination from the provider account could be seen as a fallback.
This is only one example of a tenant-aware service.
Imagine a database with a tenantId
column to store consumer specific configuration.
You can extract the value from the JWT as shown in the example:
const jwt = retrieveJwt(req);
const tenantId = jwt ? decodeJwt(jwt).zid : `No jwt given - Provider Tenant?`;
//do something for the specific tenantId