Usage Scenario
In this exercise, you will extend your CAP service with the consumption of an external Business Partner service.
Exercise Options
You can perform this exercise in two ways:
- Live Environment – using the instructions provided below, you can perform the tasks in the SAP BTP Free Tier account.
- Platform Simulation – follow the step-by-step instructions within the simulation.
Live Environment
In this exercise you will perform the following steps:
- Use the external API in the Project.
- Connect your app with the Business Partner API Sandbox Environment of the SAP API Business Hub.
- Consume an External Service in your UI application.
- Add the Business Partner Field to the UI.
Prerequisite
Before proceeding with the exercise make sure you have added custom business logic to your extension.
Steps
Use the external API in the Project.
Navigate into the
srv/external
folder.The CAP importer created the
API_BUSINESS_PARTNER.csn
file out of the EDMX file. (capire3is a compact representation of CDS). This basically contains all the schema definitions of the external service.In your project, open the
db/schema.cds
file and enter the code listed below at the very and of the file.Code snippetCopy code// using an external service from S/4 using { API_BUSINESS_PARTNER as external } from '../srv/external/API_BUSINESS_PARTNER.csn'; entity BusinessPartners as projection on external.A_BusinessPartner { key BusinessPartner, LastName, FirstName }
With this code, you create a projection for your new service. Of the many entities and properties in these entities, that are defined in the
API_BUSINESS_PARTNER
service, you just look at one of the entities (A_BusinessPartner
) and just three of its properties:BusinessPartner
,LastName
, andFirstName
, so your projection is using a subset of everything the original service has to offer.Open the
srv/risk-service.cds
file.Uncomment the
entity BusinessPartners
line.Code snippetCopy codeusing {riskmanagement as rm} from '../db/schema'; /** * For serving end users */ service RiskService @(path : 'service/risk') { entity Risks as projection on rm.Risks; annotate Risks with @odata.draft.enabled; entity Mitigations as projection on rm.Mitigations; annotate Mitigations with @odata.draft.enabled; @readonly entity BusinessPartners as projection on rm.BusinessPartners; // <--- uncomment this line }
Your SAP Fiori elements app should still be running in your web browser. Select the SAP icon on the left upper corner to navigate back to the index page. Hit Refresh in your browser. Now press on the Risks tile and in the application press Go.
The browser now shows a
BusinessPartner
service next to theMitigations
andRisks
Connect your app with the Business Partner API Sandbox Environment of the SAP API Business Hub.
At this point, you have a new service exposed with a definition based on the original
edmx
file. However, it doesn't have any connectivity to a back end, so, there’s no data yet. In this case, you do not create local data as with yourrisks
andmitigations
entities, but you connect your service to the Sandbox environment of the SAP API Business Hub for the Business Partner API that you want to use. To use the API Business Hub Sandbox APIs, you require an API key. This key will authenticate your application with the API endpoint.Attention! Treat your API keys like passwords.
Note
Pay special attention to credentials such as API keys. They are like passwords and should be treated accordingly.Go back to the SAP API Business Hub page in your browser.
Make sure you are logged in. If not, press the Log On button on the upper right.
Navigate to the Business Partner API (SAP S/4HANA Cloud → Business Partner (A2X)).
In the right upper corner, choose Show API Key to see your API key.
Copy the API key.
In your project in Business Application Studio, rename the file
.env-example
in theroot
of the project (next to filespackage.json
,README.md
, and so on) to.env
. Replace<YOUR-API-KEY>
with the API key that you copied in the previous step.apikey=<YOUR-API-KEY>
The result should look like the following:
The
.env
file is an environment file providing values into the runtime environment of your CAP service. You are going to use the API key to call the Business Partner API in the API Business Hub Sandbox environment.Note
When changing or adding variables in the .env file, you have to reserve the service. In this case you can do it by stoppingcds watch
and starting it again.By mentioning the
.env
file in the.gitignore
file you make sure, that when you are using git as a version-management-system for your project, no credentials get accidentally leaked into your potentially public git repository.The
.env
file is already listed in the.gitignore
file. You can verify this, by opening the.gitignore
file in the SAP Business Application Studio.You can also use the terminal to see the
.gitignore
file. For this, just execute the following command in a new terminal:cat .gitignore
Open the
package.json
file and add the following lines between//### BEGIN OF INSERT
and//### END OF OF INSERT
:Code snippetCopy code//### BEGIN OF INSERT "cds": { "requires": { "API_BUSINESS_PARTNER": { "kind": "odata", "model": "srv/external/API_BUSINESS_PARTNER", "credentials": { "url": "https://sandbox.api.sap.com/s4hanacloud/sap/opu/odata/sap/API_BUSINESS_PARTNER/" } } } } //### End OF INSERT
Note
Comments are not allowed in JSON-files, so do not copy the comments starting with //###.
Now that you have set all the configurations for the external call, you will implement a custom service handler for the external BusinessPartner service in the next step.
Open the
risk-service.js
file and insert the following lines between//### BEGIN OF INSERT
and//### END OF OF INSERT
:Code snippetCopy code// Imports const cds = require("@sap/cds"); /** * The service implementation with all service handlers */ module.exports = cds.service.impl(async function () { // Define constants for the Risk and BusinessPartners entities from the risk-service.cds file const { Risks, BusinessPartners } = this.entities; /** * Set criticality after a READ operation on /risks */ this.after("READ", Risks, (data) => { const risks = Array.isArray(data) ? data : [data]; risks.forEach((risk) => { if (risk.impact >= 100000) { risk.criticality = 1; } else { risk.criticality = 2; } }); }); //### BEGIN OF INSERT // connect to remote service const BPsrv = await cds.connect.to("API_BUSINESS_PARTNER"); /** * Event-handler for read-events on the BusinessPartners entity. * Each request to the API Business Hub requires the apikey in the header. */ this.on("READ", BusinessPartners, async (req) => { // The API Sandbox returns alot of business partners with empty names. // We don't want them in our application req.query.where("LastName <> '' and FirstName <> '' "); return await BPsrv.transaction(req).send({ query: req.query, headers: { apikey: process.env.apikey, }, }); }); //### END OF INSERT });
You've now created a custom handler for your service. This time it called
on
for theREAD
event.The handler is invoked when your
BusinessPartner
service is called for a read, so whenever there’s a request for business partner data, this handler is called. It ensures the request for the business partner is directed to the external business partner service. Furthermore, you have added awhere
clause to the request, which selects only business partners where the first and last name is set.Save the file. Stop
cds watch
in your terminal and start it again.In your browser, open the
BusinessPartners
link to see the data.
Consume the External Service in Your UI Application.
In this task, you incorporate the external service into the UI application.
Open the
db/schema.cds
file.Uncomment the
bp
property.Code snippetCopy codenamespace riskmanagement; using {managed} from '@sap/cds/common'; entity Risks : managed { key ID : UUID @(Core.Computed : true); title : String(100); owner : String; prio : String(5); descr : String; miti : Association to Mitigations; impact : Integer; bp : Association to BusinessPartners; // <-- uncomment this criticality : Integer; } entity Mitigations : managed { key ID : UUID @(Core.Computed : true); descr : String; owner : String; timeline : String; risks : Association to many Risks on risks.miti = $self; } // using an external service from S/4 using {API_BUSINESS_PARTNER as external} from '../srv/external/API_BUSINESS_PARTNER.csn'; entity BusinessPartners as projection on external.A_BusinessPartner { key BusinessPartner, FirstName, LastName, }
Because you got a new property in your entity, you need to add data for this property in the local data file that you've created before for the
risk
entity.Open the file
riskmanagement-Risks.csv
in yourdb/data
folder.Replace the content with the new content below which additionally includes the BP data.
Code snippetCopy codeID;createdAt;createdBy;title;owner;prio;descr;miti_id;impact;bp_BusinessPartner 20466922-7d57-4e76-b14c-e53fd97dcb11;2019-10-24;SYSTEM;CFR non-compliance;Fred Fish;3;Recent restructuring might violate CFR code 71;20466921-7d57-4e76-b14c-e53fd97dcb11;10000;9980000448 20466922-7d57-4e76-b14c-e53fd97dcb12;2019-10-24;SYSTEM;SLA violation with possible termination cause;George Gung;2;Repeated SAL violation on service delivery for two successive quarters;20466921-7d57-4e76-b14c-e53fd97dcb12;90000;9980002245 20466922-7d57-4e76-b14c-e53fd97dcb13;2019-10-24;SYSTEM;Shipment violating export control;Herbert Hunter;1;Violation of export and trade control with unauthorized downloads;20466921-7d57-4e76-b14c-e53fd97dcb13;200000;9980000230
Save the file.
If you check the content of the file, you see numbers like
9980000230
at the end of the lines, representing business partners.
Add the Business Partner Field to the UI.
You need to introduce the business partner field in the UI:
- Add a label for the columns in the result list table as well as in the object page by adding a title annotation.
- Add the business partner as a line item to include it as a column in the result list.
- Add the business partner as a field to a field group, which makes it appear in a form on the object page.
All this happens in the cds file that has all the UI annotations. Enter the code between
//### BEGIN OF INSERT
and//### END OF OF INSERT
.Open the
app/common.cds
file.Insert the following parts:
Code snippetCopy codeusing riskmanagement as rm from '../db/schema'; // Annotate Risk elements annotate rm.Risks with { ID @title : 'Risk'; title @title : 'Title'; owner @title : 'Owner'; prio @title : 'Priority'; descr @title : 'Description'; miti @title : 'Mitigation'; impact @title : 'Impact'; //### BEGIN OF INSERT bp @title : 'Business Partner'; //### END OF INSERT criticality @title : 'Criticality'; } // Annotate Miti elements annotate rm.Mitigations with { ID @( UI.Hidden, Common : {Text : descr} ); owner @title : 'Owner'; descr @title : 'Description'; } //### BEGIN OF INSERT annotate rm.BusinessPartners with { BusinessPartner @( UI.Hidden, Common : {Text : LastName} ); LastName @title : 'Last Name'; FirstName @title : 'First Name'; } //### END OF INSERT annotate rm.Risks with { miti @(Common : { //show text, not id for mitigation in the context of risks Text : miti.descr, TextArrangement : #TextOnly, ValueList : { Label : 'Mitigations', CollectionPath : 'Mitigations', Parameters : [ { $Type : 'Common.ValueListParameterInOut', LocalDataProperty : miti_ID, ValueListProperty : 'ID' }, { $Type : 'Common.ValueListParameterDisplayOnly', ValueListProperty : 'descr' } ] } }); //### BEGIN OF INSERT bp @(Common : { Text : bp.LastName, TextArrangement : #TextOnly, ValueList : { Label : 'Business Partners', CollectionPath : 'BusinessPartners', Parameters : [ { $Type : 'Common.ValueListParameterInOut', LocalDataProperty : bp_BusinessPartner, ValueListProperty : 'BusinessPartner' }, { $Type : 'Common.ValueListParameterDisplayOnly', ValueListProperty : 'LastName' }, { $Type : 'Common.ValueListParameterDisplayOnly', ValueListProperty : 'FirstName' } ] } }) //### END OF INSERT }
Open the
app/risk/annotations.cds
file and insert the following lines between//### BEGIN OF INSERT
and//### END OF OF INSERT
:Code snippetCopy codeusing RiskService from '../../srv/risk-service'; // Risk List Report Page annotate RiskService.Risks with @(UI : { HeaderInfo : { TypeName : 'Risk', TypeNamePlural : 'Risks', Title : { $Type : 'UI.DataField', Value : title }, Description : { $Type : 'UI.DataField', Value : descr } }, SelectionFields : [prio], Identification : [{Value : title}], // Define the table columns LineItem : [ {Value : title}, {Value : miti_ID}, {Value : owner}, //### BEGIN OF INSERT {Value : bp_BusinessPartner}, //### END OF INSERT { Value : prio, Criticality : criticality }, { Value : impact, Criticality : criticality }, ], }); // Risk Object Page annotate RiskService.Risks with @(UI : { Facets : [{ $Type : 'UI.ReferenceFacet', Label : 'Main', Target : '@UI.FieldGroup#Main', }], FieldGroup #Main : {Data : [ {Value : miti_ID}, {Value : owner}, //### BEGIN OF INSERT {Value : bp_BusinessPartner}, //### END OF INSERT { Value : prio, Criticality : criticality }, { Value : impact, Criticality : criticality } ]}, });
What does the code do? The first part enables the title and adds the business partner first as a column to the list and then as a field to the object page, just like other columns and fields were added before.
The larger part of new annotations activates the same qualities for the
bp
field as it happened before in the lesson: Create a CAP-Based Service4 for themiti
field: Instead of showing the ID of the business partner, itsLastName
property is displayed. TheValueList
part introduces a value list for the business partner and shows it last and first name in it.Save the file.
Open the
srv/risk-service.js
file.Add the following lines between
//### BEGIN OF INSERT
and//### END OF OF INSERT
to the file:Code snippetCopy code// Imports const cds = require("@sap/cds"); /** * The service implementation with all service handlers */ module.exports = cds.service.impl(async function () { // Define constants for the Risk and BusinessPartners entities from the risk-service.cds file const { Risks, BusinessPartners } = this.entities; /** * Set criticality after a READ operation on /risks */ this.after("READ", Risks, (data) => { const risks = Array.isArray(data) ? data : [data]; risks.forEach((risk) => { if (risk.impact >= 100000) { risk.criticality = 1; } else { risk.criticality = 2; } }); }); // connect to remote service const BPsrv = await cds.connect.to("API_BUSINESS_PARTNER"); /** * Event-handler for read-events on the BusinessPartners entity. * Each request to the API Business Hub requires the apikey in the header. */ this.on("READ", BusinessPartners, async (req) => { // The API Sandbox returns alot of business partners with empty names. // We don't want them in our application req.query.where("LastName <> '' and FirstName <> '' "); return await BPsrv.transaction(req).send({ query: req.query, headers: { apikey: process.env.apikey, }, }); }); //### BEGIN OF INSERT /** * Event-handler on risks. * Retrieve BusinessPartner data from the external API */ this.on("READ", Risks, async (req, next) => { /* Check whether the request wants an "expand" of the business partner As this is not possible, the risk entity and the business partner entity are in different systems (SAP BTP and S/4 HANA Cloud), if there is such an expand, remove it */ if (!req.query.SELECT.columns) return next(); const expandIndex = req.query.SELECT.columns.findIndex( ({ expand, ref }) => expand && ref[0] === "bp" ); console.log(req.query.SELECT.columns); if (expandIndex < 0) return next(); req.query.SELECT.columns.splice(expandIndex, 1); if ( !req.query.SELECT.columns.find((column) => column.ref.find((ref) => ref == "bp_BusinessPartner") ) ) { req.query.SELECT.columns.push({ ref: ["bp_BusinessPartner"] }); } /* Instead of carrying out the expand, issue a separate request for each business partner This code could be optimized, instead of having n requests for n business partners, just one bulk request could be created */ try { res = await next(); res = Array.isArray(res) ? res : [res]; await Promise.all( res.map(async (risk) => { const bp = await BPsrv.transaction(req).send({ query: SELECT.one(this.entities.BusinessPartners) .where({ BusinessPartner: risk.bp_BusinessPartner }) .columns(["BusinessPartner", "LastName", "FirstName"]), headers: { apikey: process.env.apikey, }, }); risk.bp = bp; }) ); } catch (error) {} }); //### END OF INSERT });
You have added another custom handler. This one is called
on
aREAD
of theRisks
service. It checks whether the request includes a so-called expand for business partners. This is a request that is issued by the UI when the list should be filled. While it mostly contains columns that directly belong to theRisks
entity, it also contains the business partner. As we have seen in the annotation file, instead of showing the ID of the business partner, the last name of the business partner will be shown. This data is in the business partner and not in the risks entity. Therefore, the UI wants to expand, that is, for each risk the corresponding business partner is also read.As the business partner does not reside in the CAP database but in a remote system instead, a direct expand is not possible. The data needs to be retrieved from the SAP S/4HANA Cloud system.
Save the file.
In your tab with the application, go back to the index.html page and press refresh.
On the launch page that now comes up, choose the Risks tile and then select Go.
You now see the
Risks
application with the business partner data in both the result list and the object page, which is loaded when you select one of the rows in the table:When you are on the object page, select the Edit button on the top right of the screen. Now you can use the value help for the Business Partner field and search for other Business Partners, which are provided via the Business Partner API.
Platform Simulation
Click on the Start button below to open a simulation of the platform. Then follow the step-by-step instructions to add an external service.
ExerciseStart Exercise
Result
You have added an external business partner service to your application. In the next unit you will find out how to deploy your application manually.