Search

Extending SaaS Applications

How SaaS subscribers can extend SaaS applications on a tenant level.

Content

Create Extendable SaaS Apps

As a SaaS application provider, you want to enable your application to be extendable by customers who have subscribed to the application. Customers can extend cds entities and services defined by the base model of the SaaS application. Those extensions will then be reflected, for instance, in tenant-specific OData metadata and SAP HANA database schemas.

Deployment Scenarios

Multitenancy and tenant level extensibility for CAP is implemented by the npm module @sap/cds-mtx. This module must be embedded into a Node.js server environment. If cds services are exposed through the cds-Node.js libraries, then cds-mtx can be embedded into the same Node.js server, which also serves the business services. If business services are served by a CAP Java server, then cds-mtx must been deployed as a separate Node.js server (“sidecar deployment”).

cds-mtx exposes tenant provisioning and extension APIs. On SAP Cloud Platform, these are consumed by SAP Cloud Platform’s Local Provisioning Service (LPS) and by the CAP Java stack when using the sidecar deployment. The extension API can also be consumed by the locally installed CDS tools used by customers.


mtx sidecar deployment with sidecar Figure: Sidecar deployment of cds-mtx when serving cds services via the CAP Java stack.

A Java server exposes OData services based on cds models using the SAP Cloud Platform SDK for service development. The Java service has been registered as a SaaS application/service on SAP Cloud Platform. If a tenant is subscribing to the SaaS application/service, LPS is triggered, which in turn calls the tenant provisioning API exposed by the SAP Cloud Platform SDK for service development. The service SDK connects to the cds sidecar if the environment variable CDS_MTX_SDC_URL is set to contain the sidecar URL. This is used to fetch updated service model data, once model extensions have been performed by cds-mtx.

cds-mtx exposes APIs for activating tenant-specific cds model extensions into the database and for generating extended metadata for consumers like the SAP Cloud Platform SDK for service development.



mtx deployment with node.js Figure: Integrated deployment of cds-mtx with other cds runtime libraries for Node.js. In this case deployment can be “all in one server”. Horizontal scaling is supported.

The cds-mtx Library

Published to npm.sap.com as @sap/cds-mtx.

Features:

Configuration

The cds-mtx library 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 example below shows a typical .cdsrc.json file with cds-mtx relevant configuration:

{
    "mtx": {
        "element-prefix": ["Z_", "ZZ_"],
        "namespace-blacklist": ["com.sap.", "sap."],
    },
    "odata": {"version": "v4"},
    "requires": {
        "db": {
            "kind": "hana",
            "model": "db",
            "multiTenant": true,
            "vcap": {"label": "managed-hana"}
        },
        "uaa": {
            "kind": "xsuaa"
        }
    }
}

In the mtx section, an array of allowed prefixes for cds element names (table/service field names) and a cds namespace blacklist for new entities and services can be configured. Field extensions can only be performed with those prefixes. Similarly, new entities and services cannot be created within the blacklisted cds namespaces. Namespace restrictions are “inherited”, that is, protecting “com.sap.” will also protect “com.sap.*”


As an SAP developer, you should protect ["com.sap.", "sap."].


The section for datasource db shows how to configure for a bound Instance Manager service for HDI containers.

The cds-mtx library exposes the “tenant provisioning API” and the model API, which is used to perform extensions (see below). There are use cases where only one of these APIs shall be exposed. This can be configured by:

"mtx": {
    "api": {
      "model":        true || false,  // default = true
      "provisioning": true || false   // default = true
}

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
Model API


Exposing the Extension API

Tenant-specific extensions can be done by customers of your SaaS application by using the cds command-line client (see Extend SaaS Applications). The client uses the /mtx/v1/model/content and /mtx/v1/model/activate endpoints. There are two options to determine the root URL for these endpoints

For the latter approach, approuter configuration in xs-app.json should be chosen as in the following example:

...
"routes": [
  ...
  {
	"source": "^/extend/(.*)",
	"destination": "sidecar",
	"target": "$1",
	"authenticationType":"none"
  }
  ...
]
...

In this case, a customer creates an extension project with the following command:

cds extend https://<tenant-specific-route-to-approuter>/extend ...

It is recommended to use the /extend/ path segment to achieve some harmonization. It is required to set "authenticationType": "none" to avoid having the approuter dealing with authentication for this route (the process of fetching a JWT is different - see Extend SaaS Applications).

Enable a Node.js Project

NPM Dependencies

To use cds-mtx with SAP HANA and SAP Cloud Platform security, include the following dependencies into package.json:

"engines": {
    "node": ">=8.0.0 <11.0.0"
    },
"dependencies": {
    "@sap/cds": "<latest release>",
    "@sap/cds-mtx": "<latest release>",
    "@sap/hdi-deploy": "^3.10.0",
    "@sap/instance-manager": "^1.4.0",
    "@sap/xssec": "^2.1.16",
    "express": "^4.16.4",
    "hdb": "^0.17.0",
    "passport": "^0.4.0"
}

Replace <latest release> by the latest cds and cds-mtx release available to you. The "node": version restriction must be set, due to version restrictions of hdi-deploy.

Server Start Script

To let your Node.js server serve the cds-mtx APIs and to connect to a multi-tenant setup for SAP HANA, use the following start script (for example, server.js):

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

// connect to datasource 'db' which must be the HANA instance manager
cds.connect.to('db');

 // serve cds-mtx APIs
cds.mtx.in(app);

// serve application defined services: in combination with a CAP Java server, this won't appear here.
cds.serve('all');

const PORT = process.env.PORT || 4004;
app.listen(PORT);

4004 is the port used when starting the server locally.


Project Structure and cds build

Follow the usual recommendations and conventions described in the getting started guide. As described, to enable cds-mtx you need to do appropriate settings in package.json and use a Node server startup script (for example, server.js).

Your project should look like this (without any UI part):

package.json         // node project definition
.cdsrc.json          // cds configuration (if not in package.json)
server.js
db/
  csv/               // csv files
  dbmodel1.cds       // cds files for db activation
  dbmodel2.cds
  ...
srv/
  i18n/              // language property files
  srvmodel1.cds      // service definition cds files
  srvmodel2.cds
  ...
handlers/            // handler implementations
  impl1.js
  impl2.js
  ...

At runtime, cds-mtx expects all cds related files to be located in folder gen/sdc/. This folder is generated when executing cds build/all. As a prerequisite, cds configuration files must include:

"build": {
    "tasks": [ {"for": "mtx", "src": "." } ]
}

Don’t check in gen/-folders to version control.

Enable a Java Project

Event Handlers for cds-mtx APIs


Note: If you are using a CAP Java server, it re-exposes the APIs required by SAP Cloud Platform’s SaaS Manager (the Provisioning API). It is recommended to leverage 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 is not 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. The server startup script (for example, server.js) needs to be enhanced to register handler implementations:

// serve cds-mtx APIs and register provisioning handler
cds.mtx.in(app).then(() => {
    const provisioning = cds.connect.to('ProvisioningService');
    provisioning.impl(require('./handlers/provisioning'));
});

This example assumes that there is a file provisioning.js in folder handlers/. Similarly, handlers can be registered for the ModelService of cds-mtx.


Use Case: Return Application URL After Onboarding

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 below example assumes that the application sets an environment variable APP_URLPART. The task of the handler is to prefix the content of APP_URLPART with the tenant subdomain contained in the onboarding request, for returning 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 is not 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 can be done by implementing 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.statusCode = 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 tenant database container deletion if certain conditions are met (for instance, there are still subscribers accessing this database). This 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

SAP Cloud Platform’s SaaS Manager might request an array of subscription dependencies (“GET DEPENDENCIES callback”). This can be implemented by registering 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;
  });
...
}

Extend SaaS Applications

Subscribers (customers) of a SaaS application can extend data and service models in the context of their subscription (= tenant context = subaccount context). New fields can be added to SAP provided database tables. Those fields can be added to UIs as well, if these have been built with SAP’s Fiori Elements technology.

The overall process is depicted in the following figure: customization process

Setup Tenant Landscape

Using SAP Cloud Platform Cockpit, an account administrator is setting-up a “landscape of tenants” (= multiple subaccounts) to enable a staged extension development scenario (for example, development and productive tenants). We recommend to setup at least a development tenant to test extended data-, service-, and UI models before activating these into the productive tenant.

Subscribe to SaaS Application

Using SAP Cloud Platform Cockpit, a subaccount administrator is subscribing to the SaaS application. During this subscription, the SaaS application automatically performs a tenant onboarding step, which (in case of using HANA) allocates a HANA persistence for this tenant (= subaccount) and deploys all database objects.


Extending cds services and database entities is only possible if the SaaS application is using the SAP HANA database service. Further, the SaaS application must have been enabled for extensibility by the SaaS provider.


Authorize Extension Developer

The extension is done by an extension developer (a customer role). The privilege to extend the base model of a tenant is linked to a scope of the SaaS application. Therefore, the security administrator of a subaccount has to grant this scope to the developer using SAP Cloud Cockpit. As a prerequisite, the developer has to be registered within the Identity Provider linked to the subaccount.

There are two relevant scopes, which can be assigned to extension developers:

Scope  
ExtendCDS Create extension projects and apply extension files. Not authorized to delete tables created by previous extensions
ExtendCDSdelete In addition, enables deletion of tables created by previous extension which can cause data loss!

The SaaS application delivers role templates including these scopes. To know these, consult the documentation of the SaaS application.

Start Extension Project

Extension developers initialize an extension project on the file system on a computer. Prerequisites:


Use the regular cds help feature to learn more about command options. For instance, to see a description of the command cds extend, use cds help extend.


The cds client is communicating with the SaaS application and fetches the “base model” (the not-yet-extended model) from there. An extension project folder is generated on the local file system. If an extension has already happened before, the last activated extension is fetched as well.

As an extension developer, initialize an extension project with the following command:

cds extend <app-url> -d <project-directory> -p <passcode>

<app-url> is specific to the SaaS application you are going to extend. This URL can be found in the documentation for the respective SaaS application. Usually, <app-url> is the same URL visible on the subscriptions tab of SAP Cloud Cockpit, which is used to launch the application, enhanced with an additional URL path segment (for example, /extend). However, the SaaS application can decide to provide a different URL for working with extensions.

<project-directory> is the folder on local disk, which will contain the extension project files.

<passcode> is a temporary authentication code, which is used to connect to the SaaS application. This passcode can be retrieved by opening a browser logon page. The URL of this browser page depends on SAP Cloud Platform landscape where the SaaS application is running and the tenant, which shall be extended:

<url> = https://<tenant-subdomain>.authentication.<landscape>.hana.ondemand.com/passcode

A passcode can be used only once and within a limited period of time. If the connection is successfully established, subsequent commands can be executed for a time period, which depends on the configuration of the Identity Provider linked to the Subaccount on SAP Cloud Platform. When expired, a new passcode has to be generated and send again.

As the connection is tenant-specific, <tenant-subdomain> must be the subdomain contained in <app-url> (the string preceding the first dot ‘.’). If not the case, the option -s <tenant-subdomain> can be used.

As a result of cds extend, an extension project is created in the specified folder. As an example, the following file/folder structure is generated on local disk:

myextproject/
  package.json    # extension project descriptor
  srv/
            # will contain service and ui-related extension cds files
  db/
            # will contain db-related extension cds files
  node_modules/
    _base/
       ...   # contains the base model provided by the SaaS application

The node_modules folder should be hidden when using an IDE, because it contains artifacts (the base cds model of the SaaS application) which cannot be changed. SaaS applications can provide templates to document how to do meaningful extensions of base entities and services.

This project structure follows the same conventions as introduced for developing entire cds applications. Model extension files, which are relevant for a database deployment must be placed in folder db/. Service extension files must be placed in folder srv/. The base model is treated like a reuse model. You can refer to it in extension files simply by using ... from '_base/...'


Extension developer should drive extension projects similar to other development projects. It is recommended to use a version control system (for example, Git) to host the extension project sources.


Develop & Activate Extensions

Developing cds model files is supported by cds editor and cds build tools. Within these files, you can reference base model files through using ... from '_base/...' statements. Entities and services can be extended using the cds extend technique. The following example shows how to add two fields to a Books database table of a hypothetical Bookshop application. A file extension.cds is created (the file name doesn’t matter) within the db-folder:

using sap.bookshop from '_base/db/datamodel';

extend entity bookshop.Books with {
  GTIN: String(14);
  rating: Integer;
}

Extensions can be activated into a test tenant using the following command:

cds activate <extension-project-directory>

This uses the current connection for activation. If this connection does not exist or has expired, then the options ‘-p <passcode>’, ‘-s <tenant-subdomain>’, and ‘--to <app-url>’ can be used to (re)connect. Activating an existing project into a different tenant requires setting <passcode> and <app-url> appropriately.


By using cds activate, it is not possible to upload csv-files into the extended tenant.



Executing cds extend on an existing Extension Project

cds extend is used to create and initialize an extension project. Subsequent executions of cds extend must be done with the --force option to overwrite existing files (base model files and extension files). No files will be deleted. Features of a version control system should be used to detect and merge changes.

Fetching extension templates from the SaaS application

SaaS applications can deliver template files which demonstrate how to extend entities and services for this SaaS application. Such templates can be fetched from the server by using the --templates option of the cds extend command. As a result, a folder tpl will be generated which contains the extension template files.


Deploy Extensions to Production

The productive tenant (the productive subaccount on SAP Cloud Platform) is technically treated the same way as any development tenant. Role-based authorization can restrict the access to the productive tenant to dedicated extension developers. An extension project, which has been generated by referencing a development tenant, can be activated into a productive tenant by using cds activate with appropriate options.