Search

    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 BTP 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 BTP. 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 BTP concepts are described here:

    CAP supports application developers in implementing the APIs required by SAP BTP. 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.:

    Java SaaS application deployment

    It provides a number of APIs for implementing SaaS applications on SAP BTP. 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 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.

    • 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"],
                "vcap": {"label": "service-manager"}
            },
            "uaa": {
                "kind": "xsuaa"
            },
            "multitenancy": true
        }
    }
    

    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"
      }
      

      Only if eventType is set to CREATE, the subscription is performed . See Develop Multitenant Applications for more details.
      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 predefined sap object, which is interpreted by cds-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 the database_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 more than one 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 (not available for HANA Cloud). 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 and extension 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 a db-folder. Extensions for services must be placed into srv-folder. Entities of the base model (the nonextended model) are imported by using ... 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 to true, 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).

    Java SaaS Application Deployment

    Prerequisites:

    To make a SaaS application available for subscription to SaaS consumer tenants, the application provider must register the application in the SAP BTP, Cloud Foundry environment through the SaaS Manager (a.k.a. “SaaS Provisioning service”).

    Each SaaS application must at least bind to two SAP BTP service instances:

    1. 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.
    2. 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 BTP’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 BTP’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 BTP 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 BTP 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 BTP’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.

    1. Enhance and add required services:
      In your package.json, add the requirement for multitenancy and vcap: {"label": "service-manager"} in db. Further, add the requirement for SAP BTP’s xsuaa authentication service.

      "cds": {
        "requires": {
       "db": {
         "kind": "hana",
         "model": ["db", "srv"],
         "vcap": {"label": "service-manager"}
       },
       "uaa": {"kind": "xsuaa"},
       "multitenancy": true
        }
      }
      
    2. Add npm dependencies:
      Add the following entries to the dependencies section in package.json:

      "@sap/cds-mtx": "^1",
      "@sap/hdi-deploy": "^3.11.4",
      "@sap/instance-manager": "^2",
      "passport": "^0.4.0",
      "@sap/xssec": "^3"
      
    3. 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.

    4. Enable authentication for the Bookshop services:

      Add one of the following alternative annotations to the AdminService (admin-service.cds) and the CatalogService (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'
        
    5. 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), and paasSubdomain (= identityzone).
    • appurl is the URL of the bookshop-srv application.

    Send requests:

    1. 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.
    2. Subscribe the PaaS tenant with request “mtx API –> Onboard PaaS tenant
    3. 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:

    1. Deploy the MTA as described previously.
    2. Run cf env bookshop-srv to obtain output similar to the following:
       [...]
       {
        "VCAP_SERVICES": {
         "service-manager": [
          {
           "binding_name": null,
           "credentials": {
             [...]
           },
           [...]
          }
         ],
         "xsuaa": [
          [...]
         ]
        }
       }
      
       [...]
      
    3. Copy the VCAP_SERVICES definition, that is, the part between and including the outermost curly braces.
    4. Create a file default-env.json in the db subfolder of your app.
    5. 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 BTP Cockpit, it must be registered with SAP BTP. 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 BTP 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 of xsappname at the bookshop-uaa-mt resource in the generated mta.yaml file.
      • <bookshop-srv-url>: this is the URL of your bookshop-srv application on Cloud Foundry. This “onSubscription” URL is used by SAP BTP 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 BTP Cockpit to create this service instance.


    Implement a Tenant Provisioning Handler

    To be finally able to subscribe using SAP BTP 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 folder srv/:

      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 your bookshop-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 BTP):

    • Using SAP BTP 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 BTP 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:

    1. Add required scopes to XSUAA configuration.
    2. Authorize Extension Developer on SAP BTP.
    3. Set up an Extension Project.
    4. 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 assign this role to you.



    Authorize Extension Developer

    • Open the overview screen of a Subaccount in SAP BTP 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. Trust Configuration

    • 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 file ext.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 field ISBN 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 section Multitenancy for the Java runtime.

    Show/Hide Beta Features