Adding an External Service

Objectives
After completing this lesson, you will be able to:

After completing this lesson, you will be able to:

  • Add and consume an external service

Add an External Service

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:

  1. Live Environment – using the instructions provided below, you can perform the tasks in the SAP BTP Free Tier account.
  2. Platform Simulation – follow the step-by-step instructions within the simulation.
Note
We are strongly recommending first performing the tasks in the live environment.

Live Environment

In this exercise you will perform the following steps:

  1. Use the external API in the Project.
  2. Connect your app with the Business Partner API Sandbox Environment of the SAP API Business Hub.
  3. Consume an External Service in your UI application.
  4. 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

  1. Use the external API in the Project.

    1. 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.

    2. In your project, open the db/schema.cds file and enter the code listed below at the very and of the file.

      Code snippet
      
      // 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 
      } 
      
      Copy code

      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, and FirstName, so your projection is using a subset of everything the original service has to offer.

    3. Open the srv/risk-service.cds file.

    4. Uncomment the entity BusinessPartners line.

      Code snippet
      
      using {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
       }
      
      Copy code
    5. 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 the Mitigations and Risks

  2. 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 your risks and mitigations 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.
    1. Go back to the SAP API Business Hub page in your browser.

    2. Make sure you are logged in. If not, press the Log On button on the upper right.

    3. Navigate to the Business Partner API (SAP S/4HANA CloudBusiness Partner (A2X)).

    4. In the right upper corner, choose Show API Key to see your API key.

    5. Copy the API key.

    6. In your project in Business Application Studio, rename the file .env-example in the root of the project (next to files package.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 stopping cds watch and starting it again.
    7. 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.gitignorefile. For this, just execute the following command in a new terminal: cat .gitignore

    8. Open the package.json file and add the following lines between //### BEGIN OF INSERT and //### END OF OF INSERT:

      Code snippet
      
      //### 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
      
      Copy code
      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.

    9. Open the risk-service.js file and insert the following lines between //### BEGIN OF INSERT and //### END OF OF INSERT:

      Code snippet
      
      // 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 
      });
      Copy code

      You've now created a custom handler for your service. This time it called on for the READ 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 a whereclause to the request, which selects only business partners where the first and last name is set.

    10. Save the file. Stop cds watch in your terminal and start it again.

    11. In your browser, open the BusinessPartners link to see the data.

  3. Consume the External Service in Your UI Application.

    In this task, you incorporate the external service into the UI application.

    1. Open the db/schema.cds file.

    2. Uncomment the bp property.

      Code snippet
      
      namespace 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,
       }
      Copy code

      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.

    3. Open the file riskmanagement-Risks.csv in your db/data folder.

    4. Replace the content with the new content below which additionally includes the BP data.

      Code snippet
      ID;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
      Copy code
    5. 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.

  4. 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.

    1. Open the app/common.cds file.

    2. Insert the following parts:

      Code snippet
      using 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 
      }
      Copy code
    3. Open the app/risk/annotations.cds file and insert the following lines between //### BEGIN OF INSERT and //### END OF OF INSERT:

      Code snippet
      using 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
           }
         ]},
       });
      Copy code

      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 the miti field: Instead of showing the ID of the business partner, its LastName property is displayed. The ValueList part introduces a value list for the business partner and shows it last and first name in it.

    4. Save the file.

    5. Open the srv/risk-service.js file.

    6. Add the following lines between //### BEGIN OF INSERT and //### END OF OF INSERT to the file:

      Code snippet
      
      // 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
      });
      Copy code

      You have added another custom handler. This one is called on a READ of the Risks 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 the Risks 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.

    7. Save the file.

    8. In your tab with the application, go back to the index.html page and press refresh.

    9. 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.

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.

Reference Links: Add an External Service

For your convenience, this section contains the external references in this lesson.

If links are used multiple times in the text, only the first location is mentioned in the reference table.

Ref#SectionContext text fragmentBrief descriptionLink
1Task FlowAdd an EDMX file to your project.EDMX OData standardhttp://docs.oasis-open.org/odata/odata/v4.0/odata-v4.0-part3-csdl.html
2Download the Business Partner EDMX FileOpen the SAP API Business Hub pageSAP API Business Hubhttps://api.sap.com/
3Add the EDMX File to the ProjectCSN is a compact representation of CDSSAP Cloud Application Programming Modelhttps://cap.cloud.sap/docs/cds/csn
4Add the Business Partner Field to the UI...as it happened before in Create a CAP-Based Service..SAP Cloud Application Programming ModelSee exercise: Create a CAP-Based Service

Save progress to your learning plan by logging in or creating an account

Login or Register