This SAP Tech Byte is about how to consume SAP BTP destinations during local development - covering both instance level and subaccount level destinations.
The source code for this blog post can be found at https://github.com/SAP-samples/sap-tech-bytes/tree/cloud-foundry-basics/post4.
Building on top of the
previous blog post of this "Cloud Foundry Basics" series, where we learned how to consume data using destinations, we will learn how to use destinations during local development today. Imagine a scenario where you want to use real backend data to test and build your application using an existing SAP BTP (Cloud Foundry) destination, instead of mocking it all locally. This blog post will cover two variations of this scenarios:
- In the first scenario we are using the standalone approuter we already created in the previous blog post alongside an instance level destination.
- For the second scenario we are using the SAP Fiori Tools - UI5 Tooling and the SAP Business Application Studio to consume a subaccount level destination locally.
The fundamental difference between those scenarios are the different types of destinations used. So before we get started, we need to make sure we have a good understanding of the difference between instance level and subaccount level destinations on SAP BTP.
Check out this
blog post for a general introduction to the Destination Service on SAP BTP, Cloud Foundry environment.
Instance Level vs. Subaccount Level Destinations
In this blog post series so far we have created and consumed an instance level destination, meaning we created an instance of the Destination Service that lives in a Cloud Foundry space:
Such a destination instance can be bound to specific standalone applications ("application binding"), for example an approuter, making it possible to call the destination from within the application (see binding information in the screen shot above). In contrast to that, subaccount level destinations are created, as the name implies, on subaccount level and therefore don't live in a Cloud Foundry space:
Such a subaccount level destination cannot be bound to specific standalone applications (that do live in a Cloud Foundry space). Consumption of a subaccount level destination therefore differs and requires different tooling. More on that further down below.
Consuming Instance Level Destinations Locally
For this first scenario we are using the standalone approuter we already created and deployed in the
previous blog posts. When running in the cloud, we can see that the approuter is serving the backend data through the destination (via
/backend😞
The approuter was bound to our instance level destination during deployment as we configured the "services" attribute in the
manifest.yaml file:
---
applications:
- name: my-web-page
...
services:
...
- backendDestination
But what if we want to work on our UI5 application locally and need the backend data for that? If where to simply start our approuter locally (via
npm start) we get an "unknown destination" error:
The fix for this is pretty straightforward, as long as we have previously deployed an app that is bound to the destination we want to call. We simply have to copy the environment variables from the deployed approuter into our local environment. Another option would be to create a service key and use its credentials. The local approuter can then connect to the destination in Cloud Foundry. The local environment variables can be set via a
default-env.json file at root level of the approuter project. We can get the environment variables for our application via the SAP BTP cockpit (or alternatively via the
cf env my-web-page command).:
Simply copy the whole "VCAP_SERVICES" object into the
default-env.json file, so it looks like this:
It is important to never (ever!) commit the
default-env.json file to source control (e.g. GitHub) as it contains client secrets and other sensitive information. It is meant for your personal development environment only. This is why we create a new
.gitignore file:
If we start our approuter now (via
npm start) and check it out, we get a weird authorization error:
This is because we have not added the new URL of our approuter (the local one) to the allowed OAuth redirects in the
xs-security.json file. This is what it looks like for me using the SAP Business Application Studio, but yours might be different (e.g.
http://localhost:5000/login/callback) as this depends on the environment you are working in (make sure to append
/login/callback😞
After editing the
xs-security.json file we have to update the service instance in Cloud Foundry in order for the change to take effect:
cf update-service my-xsuaa -c xs-security.json
After restarting our local approuter we can now see that it is serving the backend data via the instance level destination:
Note:
There is one major drawback when using this instance level approach combined with an approuter during local development, especially when you are building a frontend application and make frequent changes to your code: You will have to restart the approuter after every change you make. This can be very cumbersome. Check out the second scenario (using subaccount level destinations) down below to learn about tooling that can help with that.
Consuming Subaccount Level Destinations Locally
For this second scenario we will create a new project in the SAP Business Application Studio. We could use one of the provided SAP Fiori templates, but for learning purposes I like to recreate things from scratch, so we understand what the individual pieces do.
First, let's move the existing application from the first scenario out of the way into a separate directory called
approuter-local-instance-level-dest. Sorry for the long name, I like things specific and explicit. We can then create a new
approuter-local-subaccount-level-dest (again, sorry) directory and add new
package.json file with the following content:
{
"name": "approuter-subacc-level-dest",
"version": "0.0.1",
"scripts": {
"start": "fiori run --open \"index.html\""
},
"dependencies": {
"@sap/ux-ui5-tooling": "^1.8.6",
"@ui5/cli": "^2.0.0"
}
}
Our project structure now looks like this:
+ approuter-local-instance-level-dest/
- approuter-local-subaccount-level-dest/
- package.json
In the dependencies section we can see that we are using both the
SAP Fiori Tools - UI5 Tooling (@sap/ux-ui5-tooling) and the
UI5 Tooling itself (@ui5/cli) in our project. The combination of those tools is very powerful as they include middlewares such as proxies and a live reload for development.
The UI5 Tooling expects frontend applications to live in a
webapp directory, so let's create that. Let's also put a minimalistic
index.html and
manifest.json file containing a UI5 application inside that directory:
index.html:
<!DOCTYPE html>
<html>
<head>
<script
id="sap-ui-bootstrap"
src="https://openui5.hana.ondemand.com/resources/sap-ui-core.js"
data-sap-ui-theme="sap_horizon"
data-sap-ui-libs="sap.m"
data-sap-ui-compatVersion="edge"
data-sap-ui-resourceroots='{
"sap.cfbasics": "./"
}'>
</script>
<script>
sap.ui.require([
"sap/ui/core/mvc/View",
"sap/m/Page",
"sap/m/List",
"sap/m/StandardListItem",
"sap/ui/model/json/JSONModel"
], function (View, Page, List, StandardListItem, JSONModel) {
"use strict"
fetch("/V4/Northwind/Northwind.svc/Products")
.then(response => response.json())
.then(data => {
new View({
content: [
new List({
items: {
path: "/value",
template: new StandardListItem({
title: "{ProductName}"
})
}
})
]
}).setModel(new JSONModel(data)).placeAt("content")
})
})
</script>
</head>
<body class="sapUiBody" id="content">
</body>
</html>
manifest.json:
{
"sap.app": {
"id": "sap.cfbasics",
"type": "application"
}
}
Our project structure now looks like this:
+ approuter-local-instance-level-dest/
- approuter-local-subaccount-level-dest/
- webapp/
- index.html
- manifest.json
- package.json
The UI5 Tooling also expects a
ui5.yaml at root level of the project. This is the file where all the magic happens. We can configure the
fiori-tools-proxy middleware that will handle the connection to the subaccount level destination for us. We do so by specifying a
path and passing the unique name of the
destination - which has to live in the
same subaccount (!) as the SAP Business Application Studio we are using. This also means other development environments (like VS Code) will not work with this scenario.
We also configure the
fiori-tools-appreload middleware to benefit from live reloads of the UI5 server when file changes in the
webapp directory are being detected, which comes in very handy during development.
This is the complete
ui5.yaml file:
specVersion: "2.5"
metadata:
name: approuter-subacc-level-dest
type: application
server:
customMiddleware:
- name: fiori-tools-proxy
afterMiddleware: compression
configuration:
backend:
- path: /V4/Northwind/Northwind.svc/
destination: Northwind
- name: fiori-tools-appreload
afterMiddleware: compression
configuration:
port: 35729
Our project structure now looks like this:
+ approuter-local-instance-level-dest/
- approuter-local-subaccount-level-dest/
- webapp/
- index.html
- manifest.json
- package.json
- ui5.yaml
When checking out the app, we see... whoops! What's wrong?
We haven't configured the subaccount level destination yet. Let's go to the SAP BTP Cockpit and add the Northwind destination with the following configuration - on subaccount level:
The additional properties are very important for the connection between the SAP Business Application Studio and the destination - so make sure you add them.
Let's refresh our application:
Nice, we can see the data that was called with the help of the destination. Notice how didn't reference the domain of our backend service (
https://services.odata.org) anywhere in our application code. This proves that we are actually using the destination - otherwise we wouldn't get any data. This also means we could change the data source that our application is consuming without touching the application code, talk about
separation of concerns!
Feel free to reach out in case you have any questions.
Stay tuned for more blog posts covering the basics of Cloud Foundry.
SAP Tech Bytes is an initiative to bring you bite-sized information on all manner of topics, in
video and
written format. Enjoy!