Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
Ajit_K_Panda
Product and Topic Expert
Product and Topic Expert

 

 Blog Series
  1. Quirky Nuggets (N01-N02): CAP Event Handler, Data Uniquenes (@assert.unique)
  2. Quirky Nuggets (N03-N04): OData Operator, Undeployment of DB artifacts
  3. Quirky Nuggets (N05-N06): CF Application Health Checks, Consumption of Functions/Actions of a remote service in CAP

Introduction

This blog post is part of the ongoing series titled ‘Quirky Nuggets.’ Within this blog post, we will delve into Cloud Foundry Health Checks and execute Function or Action of a remote service in CAP Application.

Nugget 05 - CF Application Health Checks

While exploring PostgreSQL in CAP applications, I encountered an unique problem. A db deployer app is used to deploy database artifacts into PostgreSQL with the MTA deployment approach. The db deployer app correctly deployed all content into the PostgreSQL database when it started. However, the db deployer app was restarted and attempted to deploy the same content again, which resulted in a data duplication error.

After some research, I discovered that the CF environment conducts a health check within the first 60 seconds. The default health check timeout is 60 seconds, but it can be increased to 10 minutes. If the health check does not succeed within the specified timeout period, the application is considered to have failed, and the CF environment attempts to restart the application.

CF environment supports  three types of health check as mentioned below:

  • http: this health check sends an GET request to the configured endpoint and expects a response with 200 status code.
  • port: this health check makes a TCP connection to the port or ports configured for the app.
  • process: this health check monitors the process itself to see if it is running.

The default health check type for Cloud Foundry applications is port. Now let’s look at different ways to set health check type and time out period.

  • configure a health check while creating or updating an app:
    cf push <app-name> -u <health-check-type> -t <health-check-timeout>​
  • configure health check for an existing app:
    cf set-health-check <app-name> <health-check-type> --endpoint <custom-endpoint>​
  • configure health check while deploying app using MTA development descriptor (mta.yaml)
    - name: cap_postgress-db-deployer 
      type: nodejs 
      path: gen-db/pg 
      requires: 
        - name: cap-postgre-db 
      parameters: 
        buildpack: nodejs_buildpack 
        memory: 512M 
        disk-quota: 512M 
        health-check-type: process
    - name: cap_postgress-db-deployer 
      type: nodejs 
      path: gen-db/pg 
      requires: 
        - name: cap-postgre-db 
      parameters: 
        buildpack: nodejs_buildpack 
        memory: 512M 
        disk-quota: 512M 
        health-check-type: http 
        health-check-http-endpoint: /api/healthcheck

 

 

It is also possible to check the configured health check details by using following command:

 

cf get-health-check <app-name>

 

and details will be shown as below


Nugget 06 - Execute Function or Action of a Remote service in CAP

CAP enables the consumption of remote services, which may consist of APIs from various business systems such as S/4 HANA, CX Systems, or services offered by other CAP applications. If you're new to CAP or wish to review the process of consuming remote services, you can refer to the official documentation titled Consuming Services provided by CAP.

CAP Supports service consumption via:

  • import of remote service/api definitions provided via EDMX, OpenAPI or AsyncAPI file.
    cds import <path to input file>
  • send requests to remote services using the querying API
  • handle mashups with remote services, allowing integration of different data sources.
  • mocking remote services locally for testing and development purposes.

After import of the definition, define your own interface to the external service and define relevant fields in a projection. Then add your implementation which should send the request to remote services. To understand it a bit more deeply, Let’s look at how to use the S/4 HANA Cloud API for Supplier Invoice from Business Accelerator Hub.

service.cds

 

 

 

 

 

 

using {API_SUPPLIERINVOICE_PROCESS_SRV as external} from './external/API_SUPPLIERINVOICE_PROCESS_SRV.csn';

service MyService {
    entity A_SuplrInvcHeaderWhldgTax as projection on external.A_SuplrInvcHeaderWhldgTax {
        key SupplierInvoice          as InvoiceID,
            WithholdingTaxCode       as TaxCode,
            WithholdingTaxBaseAmount as TaxBaseAmount,
            DocumentCurrency         as Currency
    };
}

 

 

 

 

 

 

service.js

 

 

 

 

 

 

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

module.exports = cds.service.impl(async function (srv) {

    const { A_SuplrInvcHeaderWhldgTax } = this.entities;
    srv.on('READ', A_SuplrInvcHeaderWhldgTax, async (req)=>{
        const srvSupplierInvoice = await cds.connect.to("API_SUPPLIERINVOICE_PROCESS_SRV");
        return srvSupplierInvoice.run(req.query);
    })   

})

 

 

 

 

 

 

Supplier Invoice API has an entity A_SuplrInvcHeaderWhldgTax with 7 fields. However, in the above service definition, only 4 fields are exposed which means only 4 fields are fetched from S/4 HANA Cloud. This is achieved using projection on imported service definition. You can find more info about it here.

Supplier Invoice API also has end points to cancel or release or post an invoice which is an ‘action’ as you can determine based on property ‘kind’ from the definition in csn file which gets generated file when you import service definition.

Supplier Invoice API


cancel action in csn definition


One important aspect to highlight is that defining projections on actions or functions of a remote service is not feasible. Projections are only applicable to entities or associations. Consequently, if you want to invoke a function or action from a remote service, you must define it within your own CAP service interface.
Let’s look at cancel invoice example and see how to consume it:

service.cds

 

 

 

 

 

 

using {API_SUPPLIERINVOICE_PROCESS_SRV as external} from './external/API_SUPPLIERINVOICE_PROCESS_SRV.csn';

service MyService {

    action cancelSupplierInvoice(SupplierInvoice:String(10), FiscalYear:String(4), ReversalReason:String(2), PostingDate:DateTime) 
        returns external.CancelInvoiceExportParameters;

}

 

 

 

 

 

 

service.js

 

 

 

 

 

 

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

module.exports = cds.service.impl(async function (srv) {

    srv.on('cancelSupplierInvoice', async (req)=>{
        const srvSupplierInvoice = await cds.connect.to("API_SUPPLIERINVOICE_PROCESS_SRV");
        const input = { SupplierInvoice: req.data.SupplierInvoice, FiscalYear: req.data.FiscalYear, ReversalReason: req.data.ReversalReason, PostingDate: req.data.PostingDate };
        const response = await srvSupplierInvoice.send('Cancel', input);
        return response ;
    })    

})

 

 

 

 

 

 

In the above example, an action is specified with an identical interface as the one provided in the remote service and for implementation part, the request is sent to remote service. To define the action cancelSupplierInvoice in your interface, you can use the type structure CancelInvoiceExportParameters from imported definition .

You might perceive these extra steps as unnecessary, and I partially agree that CAP framework could potentially automate them. Nevertheless, undertaking these steps offers specific advantages. Firstly, it allows the utilization of default values for inputs, such as setting the PostingDate input to today's date or providing a default reason for ReversalReason. Secondly, this approach enables the modification of the response received from the remote service, granting greater flexibility in handling and processing the response data.

Now let’s look at another example where in remote service is an OData service provided by another CAP application and it has some functions / actions.

 

 

 

 

 

 

 

<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx">
  <edmx:Reference Uri="https://sap.github.io/odata-vocabularies/vocabularies/Common.xml">
    <edmx:Include Alias="Common" Namespace="com.sap.vocabularies.Common.v1"/>
  </edmx:Reference>
  <edmx:Reference Uri="https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.Core.V1.xml">
    <edmx:Include Alias="Core" Namespace="Org.OData.Core.V1"/>
  </edmx:Reference>
  <edmx:DataServices>
    <Schema Namespace="MyServiceExternal" xmlns="http://docs.oasis-open.org/odata/ns/edm">
      <EntityContainer Name="EntityContainer">
        <FunctionImport Name="getValuesByProperty" Function="MyServiceExternal.getValuesByProperty"/>
        <FunctionImport Name="getValuesByEntity" Function="MyServiceExternal.getValuesByEntity"/>
      </EntityContainer>
      <ComplexType Name="fn_inout_struct">
        <Property Name="name" Type="Edm.String" MaxLength="20"/>
        <Property Name="desc" Type="Edm.String" MaxLength="20"/>
      </ComplexType>
      <Function Name="getValuesByProperty" IsBound="false" IsComposable="false">
        <Parameter Name="name" Type="Edm.String" MaxLength="20"/>
        <Parameter Name="desc" Type="Edm.String" MaxLength="20"/>
        <ReturnType Type="MyServiceExternal.fn_inout_struct"/>
      </Function>
      <Function Name="getValuesByEntity" IsBound="false" IsComposable="false">
        <Parameter Name="input" Type="MyServiceExternal.fn_inout_struct"/>
        <ReturnType Type="MyServiceExternal.fn_inout_struct"/>
      </Function>
    </Schema>
  </edmx:DataServices>
</edmx:Edmx>

 

 

 

 

 

 

 

This service provides two functions: getValuesByProperty and getValuesByEntity. The getValuesByEntity function accepts an object as input and returns an object as a response (fn_inout_struct type). You also have the option to import this definition as a CDS definition and customize it by defining projections, functions, or actions according to your specific needs.

To import the input file and use it as a CDS definition, you can execute the following command:

 

 

 

 

 

 

 

cds import <input file> --as cds

 

 

 

 

 

 

 

 

using {my_service_external as srv_external} from './external/my-service-external'; service MyService { function getValuesByEntity(input : srv_external.fn_inout_struct) returns srv_external.fn_inout_struct; }service.jsconst cds = require('@sap/cds'); module.exports = cds.service.impl(async function (srv) { srv.on('getValuesByEntity', async(req)=>{ const my_service_external = await cds.connect.to("my_service_external"); const response = await my_service_external.send('getValuesByEntity', {input: req.data}); return response; }) })

service.cds


The consumption of the above function is similar to that of the Supplier Invoice Cancel action. However, I encountered difficulty in initiating the call, primarily because the function requires an object as input. Fortunately, with some assistance, I managed to resolve the issue. Now, let me explain how you can trigger the above function:

http://<host>/odata/v4/my-service-external/getValuesByEntity(input=@inputData)?@inputData={"name":"A...

Note that, @inputData is used as a placeholder and later passed as query parameter with data {"name":"A","desc":"B"}. Currently object inputs passed as shown above are not type checked.

In summary, functions or actions of a remote service can be executed similarly to a regular entity. However, it's important to note that defining projections for these functions or actions is not possible. To work around this limitation, you have the option to define your own functions or actions within your CAP application and then use them to trigger the corresponding functions or actions in the remote service. This approach allows for effective interaction with the remote functionality while leveraging the flexibility of your own application's interface.

Conclusion

In this blog post, we explored a bit about CF Health Checks and Consuming fucntions or actions of a remote service in CAP.

More information about cloud application programming model can be found here. You can follow my profile to get notification of the next blog post on CAP. Please feel free to provide any feedback you have in the comments section below and ask your questions about the topic in sap community using this link.

5 Comments
kammaje_cis
Active Contributor

Your nuggets are a great read.

I recently created a CAP based on PostgreSQL but did not configure the health-check as you specified. Here is how the deployer module looks.

I think what you need is the parameter no-start: true. This tells the CF not to start it and not to treat like other nodes apps.

Ajit_K_Panda
Product and Topic Expert
Product and Topic Expert
0 Kudos

Hi Krishna,

Yes you are right. This works too 🙂 Was thinking to write about tasks in a separate nugget. 

Thank you for reading the blog series.

Regards, Ajit

mohit_bansal3
Active Participant
Thanks for this wonderful information, It will surely help me to design some Postgres based implementations. Thanks much ajitkpanda.

Cheers;

Mohit Bansal
Ajit_K_Panda
Product and Topic Expert
Product and Topic Expert
0 Kudos
Thank you for reading the blog post mohit.bansal3  🙂
Ahmedkhan29789
Participant
0 Kudos
Hi ajitkpanda

 

Thanks for sharing these Nuggets especially the consumption of external services, however the process of using edmx not looks much feasible in the case if external api owners deleting or adding new fields to entity we have to do this cds import thing again which will mess up the code.

Is there any other way you would like to suggest like i created a destination of the api in BTP and fetch the data from that destination using some node js modules like fetch/axios etc, Not sure if it is possible, kindly share your thoughts on this.