Multitenancy
This guide explains how CAP supports multi-tenant SaaS applications and what you as an implementor and provider of such should know.
Content
How It Works
SAP Cloud Platform exposes a method for implementing and provisioning SaaS applications on Cloud Foundry. To effectively operate and run SaaS applications and to make them economically successful (TCO), such applications can be deployed once by the application provider, and then be shared by many customers. Such sharing of applications and related compute resources between many tenants is addressed by the multitenancy capabilities of SAP Cloud Platform. One important aspect is to guarantee separation between different tenants, so that a tenant isn’t allowed to access any data of other tenants. Apart from data separation, Identity and Access Management must be isolated between tenants.
These SAP Cloud Platform concepts are described here:
CAP supports application developers in implementing the APIs required by SAP Cloud Platform. If the application is using SAP HANA database, multitenancy can come out of the box. CAP features provided:
- Expose endpoints for un-/subscribing tenants, for returning subscription dependencies and for tenant database updates
- Out of the box default implementation for un-/subscribing using SAP HANA HDI containers
- Out of the box default implementation for tenant database updates
- Application can implement handlers for its own un-/subscribing logic
- Tenant-specific routing of service requests → use tenant-specific metadata and tenant-specific database connection
The cds-mtx
Module
CAP provides the npm module for Node.js published as @sap/cds-mtx
on npmjs.com.:
It provides a number of APIs for implementing SaaS applications on SAP Cloud Platform. All APIs are based on CDS. They can be exposed through plain REST, and/or consumed by other Node.js modules when running on the same server as cds-mtx
.
-
provisioning: implements the subscription callback API as required by SAP Cloud Platform. 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. -
metadata: can be used to get CSN- and EDMX-models, get a list of available services and languages.
-
model: is used to extend existing CDS models and to perform tenant model upgrades after having pushed a new version of the SaaS application.
To expose these APIs as plain REST API, embed the cds-mtx
module into a Node.js server together with cds
. Define a server start script (for example, server.js
) as follows:
const cds = require ('@sap/cds')
cds.on('bootstrap', async (app) => {
await cds.mtx.in(app) // serve cds-mtx APIs
});
// Delegate bootstrapping to built-in server.js
module.exports = cds.server
Configuration
The cds-mtx
module follows the CDS configuration method for the CAP Node.js stack. This means, configuration can be put into the package.json
file, or into .cdsrc.json
. The following example shows a typical .cdsrc.json
file with cds-mtx
relevant configuration:
{
"mtx": {
"element-prefix": ["Z_", "ZZ_"],
"namespace-blacklist": ["com.sap.", "sap."],
"entity-whitelist": ["my.bookshop.Books", "<another entity>", ...],
"service-whitelist": ["AdminService", "<another service>", ...]
},
"odata": {"version": "v4"},
"requires": {
"db": {
"kind": "hana",
"model": ["db", "srv"],
"multiTenant": true,
"vcap": {"label": "service-manager"}
},
"uaa": {
"kind": "xsuaa"
}
}
}
In the mtx
section, an array of allowed prefixes for CDS element names (table or service field names) and a list of blocked CDS namespaces for new entities and services can be configured. Field extensions can only be performed with those prefixes. Similarly, new entities and services can’t be created within the blocked CDS namespaces. Namespace restrictions are “inherited”, that is, protecting “com.sap.” also protects “com.sap.*”
The mtx
section also lists the entities and services that can be extended. The entity-whitelist
and service-whitelist
are listing the fully qualified names of the entities or services.
If these lists aren’t provided, all entities or services can be extended. A warning is logged by
cds build
in this case.
If entries of the lists can’t be resolved, cds build
fails with an error.
The section for datasource db
shows how to configure for a bound Service Manager service for HDI containers.
REST APIs
All APIs receive and respond with JSON payloads. Application-specific logic (for example, scope checks) can be added using event handlers.
Tenant Provisioning API
-
Subscribe tenant
PUT /mtx/v1/provisioning/tenant/<tenantId>
Minimal request body:
{ "subscribedSubdomain": "<subdomain>", "eventType": "CREATE" }
An application can mix in application-specific parameters into this payload, which it can interpret within application handlers. Use the
_application_
object to specify those parameters. There’s one predefinedsap
object, which is interpreted bycds-mtx
default handlers. With that object, it’s possible to set service creation parameters to be used by the service manager service when creating HDI container service instances. A typical use case is to provide thedatabase_id
to distinguish between multiple SAP HANA DBs mapped to one Cloud Foundry space.{ "subscribedSubdomain": "<subdomain>", "eventType": "CREATE", "_application_": { "sap": { "service-manager": { "provisioning_parameters": { "database_id" : "<HANA DB GUID>" }, "binding_parameters": {"<key>" : "<value>"} } } }
Having two SAP HANA databases mapped to one space, subscription doesn’t work out of the box, unless you’ve specified a default database. This can be achieved by specifying a default when mapping a database to a space. See Share an Instance with another Cloud Foundry Space for more details.
-
Unsubscribe tenant
DELETE /mtx/v1/provisioning/tenant/<tenantId>
-
Subscription dependencies
GET /mtx/v1/provisioning/dependencies
Response body:
Array of String
. The default implementation returns an empty array. -
GET subscribed tenants
GET /mtx/v1/provisioning/tenant/
Returns the list of subscribed tenants. For each tenant, the request body is returned which have been used for subscribing the tenant.
Model API
-
Get cds model content
GET mtx/v1/model/content/<tenantId>
Returns the two objects
base
andextension
in response body:{ "base": "<base model cds files>", "extension": "<extension cds files>" }
-
Activate extensions
POST mtx/v1/model/activate
Request body (example):
{ "tenant": "tenant1", "extension": "<cds extension files>", "undeployExtension": false }
The
extension
element must be a JSON array of arrays. Each first-level array element corresponds to a CDS file containing CDS extensions. Each second-level array element must be a two entry array. The first entry specifies the file name. The second entry specifies the file content. Extension files for data models must be placed into adb
-folder. Extensions for services must be placed intosrv
-folder. Entities of the base model (the nonextended model) are imported byusing ... from '_base/...'
.If the
undeployExtension
flag is set, all extensions are undeployed from the database that are no longer part of the extensions in the current activation call.❗ Warning
undeployExtension
has to be used with care as it potentially removes tables and their content from the database.Request body detailed sample:
{ "tenant": "6c07c584-f463-46e5-9340-51958c51dd0a", "extension": [ [ "db/ext-entities.cds", "using my.bookshop from '_base/db/data-model'; \n extend entity bookshop.Books with { \n ISBN: String; \n rating: Integer \n }" ], [ "db/new-entities.cds", "namespace com.acme.ext; \n entity Categories { \n key ID: String; \n description: String; \n }" ], [ "srv/ext-service.cds", "using CatalogService from '_base/srv/cat-service'; \n using com.acme.ext from '../db/new-entities'; \n extend service CatalogService with { \n @insertonly entity Categories as projection on ext.Categories; \n }" ] ], "undeployExtension": false }
-
Upgrade base model from filesystem (asynchronous)
POST mtx/v1/model/asyncUpgrade
Request body:
{ "tenants": ["tenantId1", "tenantId2", ...], "autoUndeploy": <boolean> }
Upgrade all tenants with request body
{ "tenants": ["all"] }
.If
autoUndeploy
is set totrue
, the auto-undeploy mode of the HDI deployer is used. See HDI Delta Deployment and Undeploy Allowlist for more details.Response (example):
{ "jobID": "iy5u935lgaq" }
The
jobID
can be used to query the status of the upgrade process:GET /mtx/v1/model/status/<jobID>
During processing, the response can look like:
{ "error": null, "status": "RUNNING", "result": null }
Once a job is finished, the collective status is reported like this:
{ "error": null, "status": "FINISHED", "result": { "tenants": { "<tenantId1>": { "status": "SUCCESS", "message": "", "buildLogs": "<build logs>" }, "<tenantId2>": { "status": "FAILURE", "message": "<some error log output>", "buildLogs": "<build logs>" } } } }
The status of a job can be QUEUED
(not started yet), RUNNING
, FINISHED
, and FAILED
.
The result status of the upgrade operation per tenant can be RUNNING
, SUCCESS
, or FAILURE
.
Logs are persisted for a period of 30 minutes before they get deleted automatically. If you’re requesting the job status “too late”, you get a
404 Not Found
response.
Metadata API
All metadata APIs support eTags. By setting the corresponding header, you can check for model updates.
-
GET edmx
GET /mtx/v1/metadata/edmx/<tenantId>
Returns the edmx metadata of the (extended) model of the application.
Optional URL parameters
name=<service-name> language=<language-code>
-
GET csn
GET /mtx/v1/metadata/csn/<tenantId>
Returns the compiled (extended) model of the application.
-
GET languages
GET /mtx/v1/metadata/languages/<tenantId>
Returns the supported languages of the (extended) model of the application.
-
GET services
GET /mtx/v1/metadata/services/<tenantId>
Returns the services of the (extended) model of the application.
Deployment Structure
CAP-based SaaS applications must contain the cds-mtx
library, which runs in a Node.js server environment. If the application’s business logic has been implemented in JavaScript, then cds-mtx
runs on the same Node.js server as the business logic. In this case, the server exposes the business APIs as well as the APIs of cds-mtx
. This is different, if the business logic has been implemented in Java. Then, cds-mtx
needs to be deployed as a “sidecar” to the Java server and bound to it via the environment variable CDS_MTX_SDC_URL. In this scenario, the CAP Java libraries are exposing the tenant lifecycle APIs. API calls are forwarded to the sidecar. However, any application-specific tenant lifecycle logic can be implemented in Java.
The following diagram illustrates the deployment structure for the Java case. The scenario is described for the Java case, but transfers to the simpler, pure Node.js case, with the difference that there’s just one server instead of two (CAP server and sidecar coincide).
Prerequisites:
To make a SaaS application available for subscription to SaaS consumer tenants, the application provider must register the application in the Cloud Foundry environment through the SaaS Manager (a.k.a. “SaaS Provisioning service”).
Each SaaS application must at least bind to two SAP Cloud Platform service instances:
- User Account and Authentication Service (service
xsuaa
): Binding information contains the OAuth client ID and client credentials. The XSUAA security library can be used to validate JWT token from requests and to retrieve the tenant context from the JWT. - Service Manager for SAP HANA (service
service-manager
): CAP is using this service for creating a new SAP HANA Deployment Infrastructure (HDI) container for each tenant and for retrieving the tenant-specific database connection.
Tenant Lifecycle
If a tenant is subscribing to the SaaS application, the SaaS Manager issues an onboarding request to the SaaS application. CAP triggers the generation of a new tenant database container and will deploy the database artifacts.
After running an application upgrade, the application has to trigger the “tenant upgrade” endpoint, if new or changed database artifacts shall become effective.
An “unsubscribe” event triggers the tenant offboarding endpoint. CAP deletes the corresponding database container (data loss!).
The application can influence the CAP out-of-the-box behavior by implementing own request handlers.
Runtime
Requests to CAP business services must contain valid JWT tokens which proof the authentication of the requesting user and which contains application scopes. Further, the requesting tenant ID is contained in this JWT. CAP is extracting the tenant context from the JWT and retrieves the correct database connection from the Service Manager.
Tenant-Specific Extensibility
Customers of a SaaS application can extend data and service models in a tenant-specific way. See section Extensibility for more details.
Event Handlers for cds-mtx
APIs
If you’re using a CAP Java server, it re-exposes the APIs required by SAP Cloud Platform’s SaaS Manager (the Provisioning API). We recommended leveraging the corresponding Java-based mechanisms to add handlers to these APIs. Handlers for the Model-API of
cds-mtx
must always be implemented on the Node.js server, because this API isn’t re-exposed by the Java stack.
cds-mtx
serves its APIs by using cds technology. Therefore, any additional application logic can be implemented as cds event handlers. Event handlers can be implemented in JavaScript files, which are named according to the service name. Those files can be placed into the folder containing your CDS service definition files (for example, the srv/
folder, but they can be placed anywhere where models are loaded from). In case of the cds-mtx
APIs, use the files provisioning.js
, model.js
, and metadata.js
. See the following use cases for examples.
Use Case: Return Application URL After Subscription
When connecting the tenant provisioning API to SAP Cloud Platform’s SaaS Manager, a handler for ProvisioningService
must be implemented, which returns the application URL (to be used by subscribers) for displaying it in SAP Cloud Platform Cockpit. The following example assumes that the application sets an environment variable APP_URLPART. The task of the handler is to prefix the content of the variable APP_URLPART with the tenant subdomain contained in the onboarding request. It returns the complete, tenant-specific application URL:
provisioning.js
module.exports = (service) => {
// event handler for returning the tenant specific application URL as a response to an onboarding request
service.on('UPDATE', 'tenant', async (req, next) => {
const res = await next(); // IMPORTANT: call default implementation which is doing the HDI container creation
let c = cds.env.for('app'); // use cds config framework to read app specific config node
let appuri = typeof c.urlpart === "undefined" ? ' ' : c.urlpart;
if (appuri === ' ') {
console.log('[INFO ][ON_UPDATE_TENANT] Application URI for subscriptions is not configured.');
return '';
} else {
let url = 'https://' + req.data.subscribedSubdomain + appuri;
console.log('[INFO ][ON_UPDATE_TENANT] ' + 'Application URL is ' + url);
return url;
}
});
}
A handler is registered which replaces the default handler for the provisioning service. UPDATE is relevant here, because SAP Cloud Platform sends a PUT request for onboarding. The environment variable isn’t read-in directly. Instead, the cds configuration framework is used, which allows configuring in various ways.
Use Case: Application-Specific Scope Checks
Another common task is to implement application-specific scope checks for the subscription requests. This scope check can be implemented by the “before - UPDATE -tenant” event handler within provisioning.js
:
module.exports = (service) => {
// event handler for doing some application specific scope checks
service.before ('UPDATE', 'tenant', async (req) => {
console.log('[INFO ][BEFORE_UPDATE_TENANT] ' + req.data.subscribedTenantId);
if (!req.user.is('Callback')) { // check for the scope "Callback"
console.log('[ERROR][BEFORE_UPDATE_TENANT]: JWT does not contain required scope.');
// Reject request
const e = new Error('Forbidden');
e.code = 403;
return req.reject(e);
} else {
console.log('[INFO ][BEFORE_UPDATE_TENANT]: JWT contains relevant scope');
}
});
...
}
Use Case: Conditional Offboarding
Applications may want to prevent the deletion of a tenant database container, if certain conditions are met (for instance, there are still subscribers accessing this database). This behavior can be implemented by replacing the default “DELETE tenant” implementation:
module.exports = (service) => {
...
// Event handler checking a condition before deleting the tenant DB container as a response to an offboarding request.
// This handler overwrites the default handler!
service.on ('DELETE', 'tenant', async (req, next) => {
console.log('[INFO ][ON_DELETE_TENANT] ' + req.data.subscribedTenantId);
if (1 === 2) { // put your condition here
console.log('[INFO ][ON_DELETE_TENANT]: Offboarding will not be executed');
} else {
await next(); // call default implementation which is doing regular offboarding
}
});
...
}
Use Case: Return Subscription Dependencies
If SAP Cloud Platform’s SaaS Manager requests an array of subscription dependencies (“GET DEPENDENCIES callback”), register an ON-handler for the GET mtx/v1/provisioning/dependencies
endpoint.
module.exports = (service) => {
...
// event handler returning an array of dependencies as a response to the GET DEPENDENCIES request
service.on('dependencies', async (req) => {
const deps = ['first_dependency', 'second_dependency'];
console.log('[INFO ][ON_GET_DEPENDENCIES] Dependent applications/services: ' + JSON.stringify(deps) );
return deps;
});
...
}
Node.js Stack
CAP provides the npm module @sap/cds-mtx
to expose the APIs required to build multitenant applications. This module also offers features for performing tenant-specific database and service extensions.
Deploy as Multitenant Application
This tutorial extends the Deploying to Cloud Foundry guide but can easily be applied to your own scenario.
-
Enhance and add required services:
Inpackage.json
, add themultiTenant
property to thedb
datasource and add hint to find theservice-manager
. Further, add the requirement for the authentication servicexsuaa
of SAP Cloud Platform."cds": { "requires": { "db": { "kind": "hana", "model": ["db", "srv"], "multiTenant": true, "vcap": {"label": "service-manager"} }, "uaa": {"kind": "xsuaa"} } }
-
Add npm dependencies:
Add the following entries to thedependencies
section inpackage.json
:"@sap/cds-mtx": "^1", "@sap/hdi-deploy": "^3.11.4", "@sap/instance-manager": "^2", "passport": "^0.4.0", "@sap/xssec": "^2.1.16"
-
Add server start script:
-
Add the file
server.js
to the project root folder:const cds = require ('@sap/cds') cds.on('bootstrap',(app) => { cds.mtx.in(app) // serve cds-mtx APIs }); // Delegate bootstrapping to built-in server.js module.exports = cds.server
-
Make sure to adapt the server start command in
package.json
:... "scripts": {"start": "cds run"}, ...
Alternatively, you can remove the start script declaration, because
server.js
is started by default.
-
-
Enable authentication for the Bookshop services:
Add one of the following alternative annotations to the
AdminService
(admin-service.cds
) and theCatalogService
(cat-service.cds
) of the bookshop app.- If you intend to use the Postman collection for testing (see Testing with Postman), enable access by technical users:
@requires: 'system-user'
- Otherwise, enable access by authenticated natural persons:
@requires: 'authenticated-user'
- If you intend to use the Postman collection for testing (see Testing with Postman), enable access by technical users:
-
Deploy to Cloud as described in Deploy using MTA.
Testing with Postman
Import this Collection into Postman. It contains a set of variables, which you have to set.
- From XSUAA binding information, extract
clientid
,clientsecret
,paasTenantId
(=tenantid
), andpaasSubdomain
(=identityzone
). appurl
is the URL of thebookshop-srv
application.
Send requests:
- Fetch a JWT token with request “Authenticate –> Get Token w/ Client Credentials”. Please check whether the test results are ok, indicating that the JWT token has been stored.
- Subscribe the PaaS tenant with request “mtx API –> Onboard PaaS tenant”
- Try all other requests …
Testing with Server Running Locally
You can run the bookshop server (bookshop-srv
) locally and have it connected to the remote database. To achieve this, you need to provide database connection details, which you can obtain as follows:
- Deploy the MTA as described previously.
- Run
cf env bookshop-srv
to obtain output similar to the following:[...] { "VCAP_SERVICES": { "service-manager": [ { "binding_name": null, "credentials": { [...] }, [...] } ], "xsuaa": [ [...] ] } } [...]
- Copy the
VCAP_SERVICES
definition, that is, the part between and including the outermost curly braces. - Create a file
default-env.json
in thedb
subfolder of your app. - Paste the copied data.
To start the server, run npm run start
in the root directory of bookshop-srv
.
By default, the server listens on localhost:4004
. (The port can be changed through the PORT
environment variable.)
This address can be used as server URL in the remainder of this documentation. You can also run the Postman requests against it by simply adjusting the appurl
accordingly.
Register the Bookshop SaaS App for Subscription
To enable subscription of tenants to the bookshop SaaS application using SAP Cloud Platform Cockpit, it must be registered with SAP Cloud Platform. This registration can be accomplished by creating an instance of the SaaS Registry Service. See section Register the Multitenant Application to the SaaS Provisioning Service in SAP Cloud Platform documentation for more details.
- Connect to the Cloud Foundry space to which you’ve deployed the bookshop application.
-
Create a file
regconfig.json
with the following content:{ "xsappname":"bookshop-mt-${space}", "appUrls": { "onSubscription" : "<bookshop-srv-url>/mtx/v1/provisioning/tenant/{tenantId}" }, "displayName" : "CAP Bookshop SaaS App", "description" : "A Bookshop SaaS application made with CAP", "category": "CAP" }
In this JSON file, two placeholders must be replaced with actual values:
${space}
: this is your deployment target space. Compare also the value ofxsappname
at thebookshop-uaa-mt
resource in the generatedmta.yaml
file.<bookshop-srv-url>
: this is the URL of yourbookshop-srv
application on Cloud Foundry. This “onSubscription” URL is used by SAP Cloud Platform to perform the actual subscription. It’s a “callback” into your application to perform the actual tenant provisioning.
-
Create an instance of the service
saas-registry
in your Cloud Foundry space:cf create-service saas-registry application bookshopreg -c regconfig.json
Alternatively, you can use SAP Cloud Platform Cockpit to create this service instance.
Implement a Tenant Provisioning Handler
To be finally able to subscribe using SAP Cloud Platform Cockpit, the subscription call must return a URL for an application user, which points to the tenant-specific application entry point (usually a UI). A detailed description of how to implement handlers for cds-mtx
can be found here
-
Create the file
provisioning.js
in foldersrv/
:module.exports = (service) => { service.on('UPDATE', 'tenant', async (req, next) => { const res = await next(); // first call default implementation which is doing the HDI container creation let url = '<bookshop-srv-url>/admin'; console.log('[INFO ][ON_UPDATE_TENANT] ' + 'Application URL is ' + url); return url; } ); }
In the provided code sample, you have to replace
<bookshop-srv-url>
with the URL of yourbookshop-srv
application on Cloud Foundry. The/admin
endpoint is returned then. It’s important that this endpoint isn’t protected (doesn’t require a JWT token to be called). -
Rebuild (
mbt build -t ./
) and redeploy (cf deploy bookshop_1.0.0.mtar
) your application.
Subscribe to the SaaS Application
Your application is now ready for subscription from another tenant (= Subaccount on SAP Cloud Platform):
- Using SAP Cloud Platform Cockpit, switch to, or create another subaccount within your global account.
- Switch to the Subscriptions section of the subaccount.
- Click on the tile in category CAP corresponding to your registered bookshop application.
- Click on the Subscribe button. Now, the tenant onboarding API of the bookshop application is called to provision a new SAP HANA database schema for this tenant.
- The tenant-specific application URL is returned to the SAP Cloud Platform Cockpit and put behind the Go to Application link. Usually, this URL directs a user to an application UI to log on and use the application. In our case, it points to the URL coded into the handler (
provisioning.js
) and it just brings up some service metadata.
Extend Tenant Data and Service Models
cds-mtx
provides an API to extend data and service models in a tenant-specific way. The CDS Software Development Kit, @sap/cds-dk
, contains a command line client with which an extension developer can maintain and activate cds model extensions files. See section Extending SaaS Applications for more details.
Perform the following steps to enable and perform extensions of the Getting Started Bookshop SaaS application:
- Add required scopes to XSUAA configuration.
- Authorize Extension Developer on SAP Cloud Platform.
- Set up an Extension Project.
- Create extension cds files and activate.
Add Required Scopes
Tenant-specific cds model extensions are performed by an extension developer, which is a customer role. Therefore, those activities are protected by XSUAA scopes. It’s possible to update your already existing XSUAA instance using the cf update-service
command. However, we demonstrate how to extend the generated mta.yaml
file appropriately. This has the advantage, that you don’t need to modify the generated mta.yaml
file, so that this can be regenerated without loosing your scope definitions.
Create the MTA extension descriptor file scopes.mtaext
:
_schema-version: '3'
ID: bookshop.scopes
extends: bookshop
resources:
- name: bookshop-uaa-mt
parameters:
config:
scopes:
- name: $XSAPPNAME.ExtendCDS
role-templates:
- name: ExtensionDeveloper
description: CDS Extension Developer
scope-references:
- $XSAPPNAME.ExtendCDS
role-collections:
- name: CAP-Getting-Started
description: CAP Getting Started
role-template-references:
- $XSAPPNAME.ExtensionDeveloper
Rebuild the application while applying the extension descriptor with the command:
mbt build -t ./ -e scopes.mtaext
Then redeploy while just updating the bookshop-uaa-mt
service instance:
cf deploy bookshop_1.0.0.mtar -r bookshop-uaa-mt
You need to have the
User & Role Administrator
role to be able to define role templates. If the previous command gives you a related error message, please have this role assigned to you.
Authorize Extension Developer
-
Open the overview screen of a Subaccount in SAP Cloud Platform Cockpit. This can be any Subaccount, which is already subscribed to your application. For first testing, you can choose your Paas/Provider Subaccount into which you’ve deployed your application. Note down the value for “Subdomain”. In the following, we refer to it as
<subdomain>
. It represents the tenant. -
Choose Trust Configuration and click on the active default identity provider name, for instance, SAP ID Service.
- Enter your E-Mail address and choose Show Assignments.
- Choose Assign Role Collection.
- From the popup, assign CAP-Getting-Started.
You’re now authorized to use the extension API to extend cds models for your PaaS/Provider tenant.
Set Up an Extension Project
Initialize a new extension project for tenant <subdomain>
:
- Execute
cds extend <bookshop-srv-url> -s <subdomain>
- The system will respond with the message “Invalid passcode”.
- Fetch a one-time passcode used for authentication by following the link given in the system message. Log on at the browser logon screen. Note down the shown authentication code
<passcode>
. - Execute
cds extend <bookshop-srv-url> -s <subdomain> -p <passcode>
. A project structure will be generated in a new folder with name<subdomain>
.
Create and Activate an Extension
- In the
db/
folder of this project, create the new fileext.cds
:using sap.capire.bookshop from '_base/db/schema.cds'; extend entity bookshop.Books with { ISBN : String; }
- Execute
cds activate <subdomain>
(from the folder one above the folder<subdomain>
). For the tenant<subdomain>
, the new fieldISBN
gets activated on the database. - Verify that the new field is exposed for Books. Use Postman to fetch a JWT for the extended tenant and send a GET request to
<bookshop-srv-url>/browse/$metadata
.
Java Stack
How to enable multitenancy for a CAP Java application is described in Java > Multitenancy.