Multitenancy Architecture on SAP Cloud Platform, Cloud Foundry environment
In this blog, I’ll provide you with insights into the architecture behind multitenancy on SAP Cloud Platform, Cloud Foundry environment. I’ll start with a short definition of multitenancy terminology, cover how tenants (application consumers) are onboarded or subscribed to a multitenant application, then I’ll move on to the multitenant application runtime, and conclude with offboarding or unsubscribing a tenant. I’ll also outline additional aspects that you should consider when you develop a multitenant service.
There is already a blog about how to developing multitenant applications on SAP Cloud Platform in the Cloud Foundry environment. If you aren’t familiar with it yet, I strongly recommend you that you read it first as it’ll give you a good understanding on how you can realize multitenancy for your business applications on SAP Cloud Platform.
What is multitenancy?
Multitenancy refers to a software architecture, in which tenants share the same technical resources, but keep the data separated and identity and access management for each tenant isolated.
We need to differentiate between two types of tenants when talking about multitenancy:
- Platform-as-a-Service (PaaS) tenants: Application providers get isolated platform resources to develop own business applications, either for their own use or multitenant SaaS applications that can in turn be provided to other SaaS tenants.
- Software-as-a-Service (SaaS) tenants: These tenants access multitenant applications or services to which they are subscribed. In SAP Cloud Platform, Cloud Foundry environment you can build and deploy a single instance of the application that serves multiple consumers.
Both tenants are represented as subaccounts, the PaaS tenants additionally have access to the platform-specific runtime environment, e.g., Cloud Foundry.
After the multitenant application is developed, the developer must register it to the SaaS Provisioning service (saas-registry), so that SaaS tenants can subscribe to it. you can find details in Register the Multitenant Business Application to the SaaS Provisioning Service on the SAP Help Portal.
Now, when your application is available for subscription by SaaS tenants, the application must be notified that there is a new consumer. Refer to Getting Started with Business Application Subscriptions in the Cloud Foundry Environment on the SAP Help Portal, to get more information about how SaaS subscriptions can be created.
As part of the application SaaS tenant registration, two callbacks are provided:
- getDependencies: This provides dependencies to multitenant reuse services.
- onSubscription: This allows applications to perform the tenant setup in the application. The implementation depends on the approach to separation that you use for your application.
The tenant onboarding flow looks as follows:
From the consumers subaccount the subscription is initiated to the SAP Cloud Platform. Recursively, the getDependencies() callback is called, first for the application and then for all its dependent services and their dependencies. After the complete dependency tree is built, the onSubscription() callback is called, starting from the lowest leaf until it reaches the application. After the tenant-specific persistence, if at all needed, and the configuration is created, the application returns the tenant-specific application URL that is provided in the SAP Cloud Platform cockpit.
To get more information about how to create SaaS subscriptions in the SAP Cloud Platform cockpit, read up Getting Started with Business Application Subscriptions in the Cloud Foundry Environment on the SAP Help Portal.
Example for data separation with SAP HANA
The blog I mentioned earlier describes the different options you have for separating data for your tenants, depending on your requirements. For most scenarios, we recommend choosing the schema separation as it provides the best compromise between level of separation and costs associated with it. In this next figure, you can see how you’d realize the schema separation with SAP HANA:
For each tenant, there is a separate HANA HDI container. An HDI container provides a database schema and additional support for model driven deployment of database artifacts. These HDI containers have to be mapped dynamically in your application, which means that you have to map the subaccount identifier to the corresponding HDI container. If your application also needs to keep data that is shared by all the tenants, we propose a separate HDI container to host this data.
For more HDI-related information, you can read HDI Containers on the SAP Help Portal.
To read or store tenant-specific data, the application has to get the subaccount identifier during runtime. Thus, each tenant gets its own URL that follows the pattern for development of multitenant applications:
You must configure this pattern within the application-specific application router. For more information about this task, see Application Router Multitenancy on the SAP Help Portal.
In addition, you must configure the tenant mode in the xs-security.json as “shared”. In this mode, the client secret is identical in all subaccounts that subscribe to this application. It requires the approuter variable “TENANT_HOST_PATTERN” (see above for details) and the corresponding route to be mapped.
Finally, you must set the tenant-specific route to your application with the Cloud Foundry command line interface (cf CLI):
cf map-route <approuter-name-of-your-application> <CFapp-domain> -n <subdomain>-<approuter-host-of-your-application>
For productive usage, you would use a slightly different pattern that separates the tenant and the application names into different subdomains. As a consequence, you would not have to create routes per tenant, but you would use a generic wildcard route. However, it also requires you to create a custom certificate for the application, which is something that you would typically not do during development.
When a user of your customer accesses your application in the browser, the application router derives the tenant from this URL and calls the tenant-aware UAA (user account and authentication service) that is responsible for authentication of the end user. The UAA reads the tenant, looks up the corresponding UAA identity zone that is configured for this tenant, and delegates the authentication to the IdP that is configured there. Then it creates a JSON Web Token (JWT) that contains the subaccount id for this tenant, the current user, and the authorization scopes of the user digitally signed. The JWT is then sent back to the application router, and from there to the application. When the application calls dependent services internally, it authenticates this call again with a JWT that contains the same tenant and thus the tenant is propagated through the complete stack. For more details about authorization management, read Authorization and Trust Management in the Cloud Foundry Environment on SAP Help Portal.
Whenever your multitenant application requires the globally unique subaccount identifier, it can read it from the JWT.
The detailed flow looks like this:
When a SaaS tenant is unsubscribed from your application, there must be a notification as well, which allows you to take care of data deletion and other cleanup activities. There is the same onSubscription callback triggered as for onboarding, but this time with the HTTP method “DELETE” to indicate that this tenant must be removed. Again, the offboarding call is also provided to all the dependent multitenant services that are used by your application.
Multitenancy with business services
So far, I’ve talked mostly about multitenant applications, but there are also reusable business services that might be tenant-aware. An application usually combines several services by binding to service instances of these services and subscribed to by tenants.
A service provides well-defined reuse business functionality for programmatic consumption via APIs. Services can implement data separation along the two dimensions instance and tenant as shown below:
I. Service without data separation
The service is not tenant-aware and stores the data without tenant and service separation. Therefore, the application binds against one service instance and connects to the service instance with a technical user.
II. Technical service with application data isolation
The service is not tenant-aware, but it realizes the data isolation per service instance. Applications can realize tenant-separation by creating several service instances that must be managed by the application itself. As the tenant does not need to be identified, technical users with basic authentication are sufficient.
Services that use containers to isolate data by service instance can leverage the Cloud Foundry service broker APIs to create or delete a container when a service instance is created or deleted.
III. Tenant-aware service with data shared across applications
The service is tenant-aware and is invoked with a business user. Thus, the service handles the tenant separation by itself and the data of one tenant is shared across the different applications.
Such a service is applicable for the tenant onboarding outlined for applications. This means there will be an onboarding callback wherein the service should instantiate its tenant specific storage. In case of several onboarding calls due to consumption by multiple applications, only one container shall be created.
IV. Tenant-aware services with application data isolation
The service is tenant-aware and invoked with a business user. In addition, the data is separated by service instances. This is usually the most complex option. If needed, it can be realized either by having a separate storage per tenant and a discriminator column for keeping the service instance or vice versa. For the first option, the regular tenant onboarding callback can be used to create the storage, for the latter one the creation of the service instance could serve as a trigger.
If a consuming application needs to be identified in the persistence of a service, the service instance ID (can be read from the JWT) should be used as the only identification of the consumer.
This figure shows the consumption options that I’ve just outlined.