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: 
Chandan
Product and Topic Expert
Product and Topic Expert
2,628
In this blog post, we will deep dive into the process of constructing a comprehensive use case using the powerful capabilities of the SAP Cloud Application Programming Model (CAP).

Our focus will be on consuming a REST service within CAP, and I draw inspiration from a highly informative blog by Robert Witt that greatly helped me in developing this application.

Let's provide a summary of the exciting journey that lies ahead of us. “I can’t wait 😁

Use Case


In our application, I have defined a data model consisting of two main entities: Customers and Locations. A single customer can operate in multiple locations, and each location has an attribute called "temperature." Our goal is to ensure that this attribute is regularly updated with the current temperature for each respective city.

To achieve this, we require a service capable of providing us with the latest temperature data. Fortunately, there are several services available for this purpose, and for our use case, we will be utilizing the OpenWeatherMap service.

Also, we will explore how to develop an action within CAP that will interact with the OpenWeatherMap REST service. This action will retrieve the current temperature information and subsequently update our data model with the latest temperature values.

Now, let's dive into the process of building this application, starting with the definition of our data model.

Data Model


type Coordinates : {
longitude : Decimal(9,6);
latitude : Decimal(9,6);
};

entity Customer {

key name : String(20);

}

entity Location {

key city : String(20);
coordinates : Coordinates;
temperature : Decimal;
temp_unit : String(2);
}

entity OperatedIn {

key customer : Association to one Customer not null;
location : Association to one Location not null;
}

We have a straightforward data model that revolves around two primary entities: Customer and Location. Additionally, we have a local type known as "Coordinates," which is declared within the same service.

The Location entity has an attribute called "temperature." This attribute holds the current temperature specifically associated with each location. It allows us to store and track the temperature data effectively.

Service


using { OperatedIn as OperatedIn } from '../db/schema.cds';

service sensorData {

@readonly entity CustomerLocations as select *, location.temperature from OperatedIn;

// Take the City as input and call the weather API to update the current temperature
action updateTemperature(city: String(20)) returns Decimal;

}

Within our service, we have an unbound action called "updateTemperature". This action is responsible for handling the process of calling the OpenWeatherMap service to update the temperature data.

The updateTemperature action accepts the location name as input. It then reads the database to retrieve the geo-coordinates associated with that specific location. With the acquired coordinates, the action proceeds to invoke the OpenWeatherMap REST service.

Consuming the REST Service


In the package.json you have mentioned the service details.
"cds": {
"requires": {
"weatherservice": {
"kind": "rest",
"credentials": {
"url": "https://api.openweathermap.org",
"requestTimeout": 30000
}
}
}
}

Custom Handler


//  Take the City as input and call the weather API to update the current temperature 
srv.on('updateTemperature', async (req) => {

const {city} = req.data;

const db = await cds.connect.to ('db');
let {Location} = db.entities;

let results = await db.read(Location,["city","coordinates_longitude as longitude","coordinates_latitude as latitude"]).where({city:city});

const weatherAPI = await cds.connect.to("weatherservice");
// Using template literals
let res = weatherAPI.tx(req).get(`/data/2.5/weather?units=metric&appid=<ApiID>&lat=${results[0].latitude}&lon=${results[0].longitude}`);

let final_data =
res.then( async (data) => {
let results1 = await db.update(Location).set({temperature: data.main.temp}).where({city:city});
return data.main.temp;
});

return final_data;

});

When the action is invoked, this custom handler comes into play. It performs a sequence of operations to ensure the successful retrieval and update of temperature data.

Firstly, it reads the coordinates associated with the input location from the database. Then it calls the designated service for temperature data retrieval. Once the temperature is obtained, it is both updated in the database and returned as the current temperature.

It is important to note that, you need to obtain an API ID from the OpenWeatherMap website. Kindly replace "<ApiID>" with your unique API ID provided by OpenWeatherMap.

If you are wondering how to use the OpenWeatherMap services then please refer to its documentation API doc.😊

Summary


Key Takeaways from this Use Case:

  1. Understanding the process of consuming a REST service within CAP.

  2. Learning how to effectively read and update the database.

  3. Implementing an action in CAP to perform specific tasks.


Note:📝 The codes in this example are purely for illustrative purposes. No performance testing has been done. There may be alternative approaches for optimization.

Thank you. Hope you enjoyed this blog! Please feel free to leave a comment or question below.