Skip to Content
Technical Articles

Getting your head into Cloud Application Programming model multitenancy

This blog post provides a bit of discussion and context to support a Cloud Application Programming(CAP) model sample code project that implements multitenancy via the CDS-MTX library in SAP Cloud Platform Cloud Foundry.  If you just want to start with a working example then you can go straight to the sample.  However, if you prefer a little background before diving in, then let’s begin.

Multitenancy support has been around for some time

 

Multitenancy as a topic has been available since SAP’s first Cloud Offering which today is called Neo. In SAP Cloud Platform Cloud Foundry, many of the services that SAP provides are available by “subscribing” to them and are thus multitenant. Some of these include the Business Application Studio a cloud based IDE.  And while it isn’t necessarily new, the topic of multitenancy seems to be beyond the reach of many cloud developers.

There have been blog posts that have covered this topic from conceptual standpoint such as Developing multi-tenant applications on the SAP Cloud Platform and Developing Multitenant Applications on SAP Cloud Platform, Cloud Foundry environment as well as videos Hands-on Video Tutorials for Developing Multitenant Business Applications and even other sample applications Using SaaS Provisioning Service to develop Multitenant application on SAP Cloud Platform, Cloud Foundry Environment and I’ve even had a go at a more manual deploy time approach with [Custom Schema Separation in Multitenant Applications.

While technically correct, the standard documentation is a bit scattered with snippets of various aspects of a multitenant application in a combination of Node.js and Java languages.  See Developing Multitenant Applications in the Cloud Foundry Environment for a good starting point with the official docs.

Where to persist?

 

 

In a multitenant application it is key to separate each subscriber/client’s data from any other client’s data.  If you’re storing this data in a single database schema, you’d have to create a column that identifies which rows of data belong to which client and be super careful about forming your select statements as to not disclose the wrong client’s data.  For many business cases, this isn’t enough separation.  You could give each client it’s own database, but that would likely be too costly.  The balanced approach is to give each client it’s own schema.  Since SAP uses HANA as its primary persistence store, internally multitenant projects used a managed_hana service broker, but that mechanism was never offered to customers/partners for use in creation their own solutions.  Recently a new service called the Service Manager has been introduced that can handle a much broader set of situations but still includes dynamically managing HANA instances as part of it’s overall function set.  As a result we can now distribute a complete code sample project that deploys out of the box and doesn’t relay on any SAP internal-only servics.

What CAP offers

 

The new Cloud Application Programming model offers a more generalizable way of defining a project’s data model and services definitions and can create HANA specific output.  It also has been extended to handle a lot of the tasks needed to build enterprise grade multitenant applications.  You can find all the details and a general description of the CAP methodology at the project’s documentation site.  Note that this documentation is separate from the main SAP documentation and is released on a monthly cycle.  The CAP project is moving fast and the paint may not always be completely dry.

Working End-To-End

 

I’ve recently published a sample code project that implements a completely working end-to-end example of a multitenant application that is intended to be used as a starting point for your multitenant projects.  There are many little details that if gotten wrong lead to a frustrating experience with multitenant development under CAP and my goal is to get you started on solid footing.  I also wanted to show how multitenancy is done in CAP since it’s difficult to mix and match the CAP approach with earlier examples described in the standard documentation.  This example is pure CAP and relies on the cds-mtx library for it’s multitenancy support.

Cloud CAP Multitenancy SAP sample application

CAP model requires some study first

 

While most will want to jump in and deploy the code sample project and start tweaking it, I emplore you to study up on CAP itself first in order to get some of it’s core concept in your head.  This is time well invested and will help mitigate future frustration.

Start with the About section and read through each of the following sections in order.  Pay special attention to the Related Concepts section.  I know this will take some time, but remember that this is an investment, don’t just gloss over it.

You may be tempted to jump into the Cookbook section and start hacking away, but I advise against this.  The Cookbook section did help me solidify my understanding of some of the concepts introduced.  Take special note of the Cookbook -> Providing Services

section and especially the Registering Event Handlers section as this is key to using the multitenancy features of the cds-mtx library we’ll discuss shortly.

How to approach multitenancy in CAP

 

This blog post focuses on using the Node.js versions of libraries for CAP development.  If you are using Java, refer to the documentation directly.

I know you might feel like you’re skipping around the docs, but please read and understand the section on Runtime Configuration for Node.js .  It’s important to fully understand how the CDS object is defined in Node.js and how it’s processed by various mechanisms(hidden and explicit) and how it inherits it values from files in parent folders.  Always double check your assumptions by running the command line version of the CDS tool to verify your assumptions.

From the docs: “Use cds env to list the effective configuration, which has all configuration files and profiles merged.” 

cds env

You should see some output similar to this.

$ cds env
_context = cds
_home = /Users/xxxxx/git/cloud-cap-multitenancy
_sources = [
  '/usr/local/lib/node_modules/@sap/cds-dk/node_modules/@sap/cds/lib/env/defaults.js',
  '/Users/xxxxx/git/cloud-cap-multitenancy/.cdsrc.json',
  '/Users/xxxxx/git/cloud-cap-multitenancy/package.json',
  '{process.env}'
]
build.target = .
build.tasks = [
  { for: 'hana', src: 'db', options: { model: [ 'db', 'srv' ] } },
  { for: 'node-cf', src: 'srv', options: { model: [ 'db', 'srv' ] } },
  { for: 'java-cf', src: 'srv', options: { model: [ 'db', 'srv' ] } },
  { for: 'mtx', src: '.', dest: 'srv' }
]
comment = Anything defined inside of [development] will overrided prior values.
env = development
features.fiori_preview = true
features.localized = true
...

Thinking about your onboarding process

 

Before you dive headlong into getting your multitenant CAP application up and running, you should think about what your subscriber onboarding process will look like.  Here are some considerations.

  • Will your customers be able to sign up in a self-service way or will they be manually onboarded?
  • What sort of information will onboarding require?  Signed contract?  Credit card number? Existing SAP Cloud Platform account information?
  • Will your multitent solution be completely provided within SAP’s platform or will it contain components hosted elsewhere?
  • Where will your customer’s users be defined?  Who will manage giving them permissions mapped into your solution’s capabilities?
  • Do you need to coordinate your Identity Providers with multiple services to attain a smooth single-sign-on experience?
  • Will there be routing table changes? DNS entries added or modified?
  • Will the definition of Roles and Role Collections and the assignment of them be automated or manual?
  • What other operations will need to be performed?  Files written, Emails sent, Approvals granted?
  • Lastly, how will you unsubscribe a customer?  Will you delete all of their data?  Archive it?  Continue to charge for read-only access to archived data?

By considering all the kinds of things that are needed to happen when onboarding a subscriber, you start appreciating that it can be a complex and time consuming process with a lot of potential dependencies.  While the discussion of these items would cover another full blog post, we won’t be getting into that here.  Just understand that these issues and what you decide about them will greatly shape your onboarding process and why you will want to make it your own.

 

Setting the Stage

 

Before we get into working with the code sample itself, let’s prepare the trial account for deployment and subscription testing.

Let’s create a new subaccount that is where we will deploy the code sample project.

Give it the name provider and set the Provider and Region to the same values as your trial account.

Give it a subdomain name that is likely going to be unique in the whole landscape where you’re deploying.  I’m using the first 4 characters as my trial account name but you can use something else as long as it’s unique.

Do the same for 2 more subaccounts that we’ll use for testing.  I’ll call them sub1 and sub2.

When you’re done your account should look like this.

 

By default the trial account will have all the Application Runtime quota assigned to the dev subaccount.  We’ll need to borrow a couple of gigs from the trial subaccount and give it to the provider subaccount.

Go to Entitlements : Entity Assignments, pick the trial subaccount and click the Go button.

Click the Configure Entitlements button.

Reduce the setting by 2 and click the Save button.

Now do the same for the provider subaccount but this time assign the Application Runtime quota.

The provider subaccount entitlements should now look like this.

Next, click on the Enable Cloud Foundry Button to well, enable Cloud Foundry.

It may take a few minutes, but once Cloud Foundry is enabled, Create a new space and call it dev.

Getting and Deploying the Code Sample

 

If you haven’t already, go to the trial subaccount and then Subscriptions and subscribe to the Business Application Studio.

Next, give your user additional Roles by clicking the sap.default link.

When using sap.default, you have to explicitly type in your user email an click Show Assignments before you are able to add any new Roll Collections.

Make sure your user has the rolls Business_Application_Studio_Administrator and Business_Application_Studio_Developer.

 

Then go back to Subscriptions and click Go to Application.

 

Restart your dev space or start a new one and then select it.

Make sure you’ve logged into Cloud Foundry and are targeting the provider org and dev space.

Open the terminal and make sure you’re in your projects folder.

Use git clone to grab the latest version of the code sample repository.

git clone https://github.com/SAP-samples/cloud-cap-multitenancy.git

Change into the cloud-cap-multitenancy directory.

cd cloud-cap-multitenancy

There is a COMMANDS.md file that has a handy set of commands for building and deploying the application.  Open it in preview mode for convenience.

Create a folder to store the resultant mtar files and build the mtar.

mkdir -p mta_archives ; mbt build -p=cf -t=mta_archives --mtar=capmt.mtar

If all went well you should see this in the output.

 

 

Now be sure you’re logged into the trial account with Cloud Foundry and are targeting the provider org and space.  Deploy the mtar.

cf deploy mta_archives/capmt.mtar -f

Where to hook into CAP

 

Find the Multitenancy section under the Cookbook in the docs.  This section introduces the cds-mtx library which seems to be loosely dependent on the main CDS library but is release independently.  There is no explicit dependency in it’s package.json file so you’ll need to consult the CHANGELOG.md files of each library to see if they match up or not.  The versions used in the sample project are know to work together but may not be the latest for each one.  The point here is to keep this in mind if you find that upgrading suddenly causes things to fail.

You MUST customize the handling of onboarding subscriptions in order to at a minimum tell the Software as a Service(SaaS) registration system what URL to give to a new subscriber to uniquely identify them vs. other subscribers.  While the Implement a Tenant Provisioning Handler section states this is as simple as adding a provisioning.js file next to your services definition.js file,

I couldn’t get that to work no matter what I tried.  After talking to the development team it was recommended that I hook into the provisioning callbacks more explicitly.  You can see where my example code differs from the documentation example.

And then my code implements this handler in the /srv/handlers/provisioning.js file.

There’s a lot more going on in this provisioning.js file and I’ll get to that next.

One thing I can’t stress enough is watching the logs of your service module to see what’s going on during a subscription attempt.  Since this by definition happens outside of the normal operation of your solution, it’s tough to get an idea of what is going on and if things are failing.

If you look in the project mta.yaml file you’ll see that I’ve defined an environment variable called NODE_DEBUG.  This sets the logging level of the mentioned library.  Here ‘instance-manager’ is the component that does the provisioning and defining it here enables a bunch of logging in the output.

Another thing to keep in mind is that if things go bad the subaccount that you’re using to test with may get stuck in a PROCESSING state that will prevent subsequent subscription attempts.  I’ve found that I just had to create other subaccount and abandon the “hung” ones.  Eventually they seem to revert to a state where additional subscription attempts can be made, but I couldn’t determine exactly when that was.  Once your subscription logic is working, you can attempt to go back and resubscribe/unsubscribe them to clear up the entry in the SaaS system.

Plotting the route

 

Once your subscription callbacks are working properly and you’re returning the URL back to the SaaS registry system,  you’ll see that the Go to Application URL link is now enabled and ready for action.  However, when you click it, you’ll then be met with the following error.

The problem here is that while the SaaS subscribeability callbacks are working, the additional step of creating a route that points that specific hostname back to your application has not been created as part of the onboarding process.  It’s up to you to decide how and when that’s done.  As we discusses above in onboarding process thought process, you may want to handle this in different ways.

We can manually do is with the cf command.

But likely it would be desirable if the route just got created automatically upon subscription.  To do this our multitenant app would need to know your Cloud Foundry credentials.  It could then call the API and perform the routing commands on your behalf.  Since including credentials in source code and committing the to a public repo is a definite no-no, I provide a template file that you can customize for yourself and set your own environment properly.

For Linux-like systems(including Business Application Studio in the cloud) you can rename the CF_CREDS.txt file ot CF_CREDS.sh and give it execute permissions.  For windows, you’ll have to convert it into a cmd file or just execute the commands manually.

mv CF_CREDS.txt CF_CREDS.sh
chmod 755 CF_CREDS.sh
./CF_CREDS.sh

The net effect is that CF_API_USER and CF_API_PW are properly defined into your deployed environment and since I .gitignore the CF_CREDS.sh file, it shouldn’t get accidentally committed.  A better solution would be to use some for of encrypted key-value store service.  Checkout the Credential Store for more info on it’s use, or the HANA SecureStore features.  Now after the subscription is completed, clicking on the Go to Application link, you’ll be taken to the application’s app-router page.

Assigning the permissions

If you then click on one of the two catalog links, you’ll be required to authenticate.  To do this the application needs to redirect you to the login as configured in the security settings of the subscriber’s subaccount.

BTW, this is the function of the TENANT_HOST_PATTERN string as defined in your mta.yaml file.

The first part of the hostname is parsed(the subdomain to be specific) and is used to direct the user to the proper authorization endpoint for that subaccount.

If you’ve done no other setup in the Security setup of your subaccount, you’ll be greeted with the following in the browser even after successfully authenticating.

The reason for this is that your user hasn’t been granted the proper Roles by virtue of being assigned to the proper Role Collections.  This can be done automatically if the subaccount has a custom Trust Configuration set up and there is a valid group mapping of users who reside in groups as defined in the external identity provider with the appropriate Role Collection that determines what permissions that user is assigned.  However, if you’re using the default SAP ID service, it isn’t possible to do this.  You can manually do these tasks on a per subaccount basis.

First as a Security Administrator, go into the subaccount from which you’re subscribing and click Role Collections and the + icon.

Give your Role Collection a name.  Here I’ve used “CAP Multitenancy” and click Create.

Now click on link/label of the Role Collection you just created.

Add click Edit to get into edit mode.

Make sure you’re on the Roles tab and click the + icon to add a Role to this Role Template.

Select the User Role from the dropdown and click the Save button.

No go to the Trust Configuration page and click on the SAP ID Service link.

Type in the user’s email address as it’s known to the SAP SSO system.  This is the user associated with your S or P user and click Show Assignements.

Click on the Assign Role Collection button which should now be enabled.

And FINALLY, select the CAP Multitenancy Role Collection and the Assign Role Collection link.

Wow that was tedious!  And it would be difficult to automate.  Since using the SAP ID Service doesn’t allow you to define groups that could be pre-mapped to Role Collections, You can see the advantage of using your own identify provider so that you could only have to provide that mapping of user groups to Role Collections.

In the future, this code sample may be extended to utilize the upcoming sapcp API to automate the above tedium.

Now when you click Go to Applicaiton, and then the /catalog/ link…

…and you’re authenticated with the proper Roles, you’ll get the proper output.

Testing and debugging

 

Keep in mind that most of what’s happening during the subscription/unsubscription process is happening behind the scenes.  You can watch the logs with the cf logs command.

 

cf logs capmt-srv

 

Also, since it’s difficult to replicate the environment in which the service module is interacting with the subscription system, I recommend that you use the cf ssh command to make temporary changes to your capmt-srv module in order to troubleshoot your issues.  Just make sure to keep and note of what you’ve changed and incorporate it back into your project source code for the next deployment.

 

cf enable-ssh capmt-srv
cf restart capmt-srv
cf ssh capmt-srv

Enlisting Yeoman

 

You might want to check out the documentation/YO_RECIPE.md file. It describes using the yeoman template generator to frame up the application as opposed to cloning it from git. The advantage is that it detects the landscape you’re logged into so will adjust the hostnames accordingly. Plus it prompts you for most things so that you can generate a project that aligns with your code project file naming conventions to make it easier to combine with existing projects without complicated/error prone refactoring.

Conclusion

 

I know I started kinda slow in explaining things about how CAP implements multitenancy.  I’m really just for your own self preservation.  CAP is a model, a methodology, and nearly a way of life.  It’s super flexible and extendable but with all that power comes a lot of complexity.  While it’s tempting to “jump right in” and get something going, that can also lead to endless frustration.  I’ve tried to give you a path to approaching this small portion of what CAP can do for you in a multitenancy use case  without getting to far into the weeds.  My hope is that this post along with the accompanying code sample put you and your project on the path to success.

 

Let me know if you have and questions or issues by leaving me a question below or better yet, asking it on the SAP community.

-Andrew

Partners: If you have a question, click here to ask it in the SAP Community . Be sure to tag it with Partnership and leave your company name in the question so that we can better assist you.

 

13 Comments
You must be Logged on to comment or reply to a post.
  • Thanks for the great blog and repository, Andrew, that came in just-in-time when it was needed 😀

    you might want to add a note that .us10. domains are hardcoded in a couple of places, which may need to be adjusted by the fellow developers before deploying to CF

  • Hi Andrew,

    after implementing this approach, I realized that neither the hdi-shared container nor the db-deployment module are utilised at all..

    so you could completely drop the capmt-hdb and capmt-hdi from mta.yaml

    Best regards,
    Sergei

    • My intention was to illustrate that a typical multitenant application would likely need to persist information in a central way (common data, other subscriber info, etc.) and that it’s tricky to get the synonyms for joining that table with the tenant provisioned tables worked out.

      However, I haven’t gotten back to that yet.  If you find it’s bogging down your deploys, you can remove it.

      Consider the code sample a living thing.

      I’d also like to demonstrate utilizing the SAPCP API for creation of Roll Collection, assignment of Roll to Roll Collection, and assignment of users to Roll Collections.  As that has to be done manually per subscriber subaccount currently.

      End goal is a completely self-service provisioning process with no manual steps.

      -Andrew

  • Hi all,

    The mtx library alone is capable of generating new HDI containers (triggered by the provisioning API) and then deploying HDI content into it. So, no need for a separate HDI deployer app, indeed. However, I think that Andrew is addressing an important use case, that there is tenant independent (probably administrative data) which is maintained by a SaaS application provider administrator. This indeed could be realized by a seperate hdi deployer app (with its own hdi content) bound to a separate ‘hana’ service instance. The challange is currently, that CAP projects do not support two different db data sources out of the box. Maybe this will change at one point in time, so that one of the db data sources can be a regular ‘hana’ service instance, whereas the other one would get the flavor ‘multitenant’ and become managed by the mtx-library.

    So, good idea to consider this. Maybe one could work on this in an advanced version or branch of Anrew’s sample project.

    Another option by the way would be to introduce the PaaS-tenant HDI container (the one which can be onboarded with Provider-Tenant credentials) as the container which hosts the administrative part. Then, the administrative part would be included into the overall cds data model.

    -Klaus

    • Klaus,

      Thanks for adding your experience and ideas to the discussion.  These are exactly the use-case discussions I’d like to see more of in the community.  Seems we all to often leave it as an “exercise for the user” when we should be offering more curated guidance based on our experiences with customers/partners.

      -Andrew

  • After having faught with multitenancy in CAP for a while now (with breaks again and again) and having MTX now at hands, which now works like a charm thanks to Andrews Blog and Comments,

    I indeed postponed the topic of “having subscriber-independant data somewhere” to later – in a manner of “if that doesn’t work internally, I could just put up a separate non-multitenant CAP app, which is connected via a destination.

    So I didn’t try that yet. But it absolutely makes sense to put it into a “plain” hdi container bound to the app.

  • Hi Andrew,

     

    Great blog – I have been passing it around to my team as required reading! It helps explain multitenancy in a clear manner (insofar as that is possible to do in a “SaaSaaS” framework)

    I’m diving into the code now and on my first naked clone + build + deploy (per the COMMAND document) I’m running into this:

    Service operation failed: Controller operation failed: 502 Updating service "CAPMT_REG" failed: Bad Gateway: Error creating service "CAPMT_REG" from offering "saas-registry" and plan "application": CF-ServiceBrokerBadResponse(10001): Service broker error: Service broker saas-registry failed with: Service broker parameters are invalid: field: appName - must match "^\s*\S+\s*$"

    Where would I begin troubleshooting this one? I tested the entry i have there currently (the default from your repo) against the regex string it mentioned, and it seems to match fine. I haven’t been able to locate the SaaS Provisioning service docs to investigate further of the exact MTA parameters.

    For context, I’m very familiar with CAP, MTAs, and SAP Cloud Foundry/Neo services, licensing, etc. Last piece for me is true multitenancy.

    Any assistance is appreciated!

    Matt

  • Hi Andrew,

    Great blog. Thanks for the detailed information.

    It is working great. I have one question on the HDI containers. while the containers are getting created in the database, they are not showing up to add in the Database explorer. can you give some details if it is possible to add the created HDI containers in the database explorer?

    Thank you,

    Venu