Skip to Content
Event Information

Annotated links: Episode 21 of Hands-on SAP dev with qmacro

This is a searchable description of the content of a live stream recording, specifically “Episode 21 – CAP, Java, Maven and the cloud” in the “Hands-on SAP dev with qmacro” series. There are links directly to specific highlights in the video recording. For links to annotations of other episodes, please see the “Catch the replays” section of the series blog post.

This episode, titled “CAP, Java, Maven and the cloud”, was streamed live on Fri 26 Apr 2019 and is approximately one hour in length. The stream recording is available on YouTube.

Below is a brief synopsis, and links to specific highlights – use these links to jump directly to particular places of interest in the recording, based on ‘hh:mm:ss’ style timestamps.

Brief synopsis

Having already laid the foundations for a Java project within the framework of the SAP Cloud Application Programming Model in Episode 18 and Episode 20, we are now continuing with the implementation of Custom Logic. Since at the end of the previous episode we obviously encountered an error in our coding (exception in our HTTP request via Postman to our backend service), we dare to take a look at the logs to fix the error. To understand the basics of our Java project, we take a step back and look at the build process with Maven.

00:00:10: DJ Adams again introduces me and routinely welcomes the stream family

00:02:30: Giving a little glimpse about where we left off last time and what’s going to happen today!

00:03:50: Shoutout to the SAP CoffeeCorner folks (@uxkjaer and @skemp) who are going to SAPPHIRE NOW 2019 and keeping us up-to-date with their excellent podcast!

00:04:30: Curly brackets are not anymore called curly brackets. Am I the reason for moustache/max brackets? 😉

00:05:20: As part of the News section in the beginning of DJs livestream he introduces Ivan Femia  with his YouTube series called fromZeroToCloud with all sorts of content, covering theoretical and practical aspects of the SAP Cloud Platform!

00:06:50: Yet another shoutout to another twitch streamer called Garreth Hubbal who streams for instance about F# (also a functional programming language).

00:07:50: Talking about Matt Harding’s Tweet discovered about SAP Fiori 3. We also have a look at the Sandbox Fiori Launchpad with an implementing Demo Application.

00:10:30: Did you know that there is a openSAP course starting about SAPUI5 soon? Have a look at the course and challenge yourself! Short description of the according course: “We start with a quick catch up on the UI5 basics and our evolved best practices as well as new tools. Then we dig into more advanced scenarios and concepts step by step. You’ll not only sharpen your UI5 development skills, but also explore additional options to increase your developer productivity with SAPUI5.”

00:11:00: Nabheet experimented with CAP and Google Cloud Run. Curious about the result? Reading his blog is a must!

00:13:00: The Visual Studio Code extension for the CDS Language Support received an update to 1.1.4. Get your newest version here!

00:18:00: Going over the content we created in the previous Friday stream: We basically started a CAP project with some custom logic written in Java using SAP Cloud Platform WebIDE supported by a SAP HANA Database in SAP Cloud Platform! We have a look at all the service and entity definitions done with CDS and the Java methods we implemented to add some custom logic on top of the generic operation. We basically had two methods.

  1. A method to manipulate data after the generic operation has already read data from SAP HANA.
@AfterRead(entity = "Orders", serviceName = "CatalogService")
public ReadResponse afterReadOrders(ReadRequest req, ReadResponseAccessor res, ExtensionHelper helper) {
   EntityData ed = res.getEntityData();
   EntityData ex = EntityData.getBuilder(ed).addElement("amount", 4711).buildEntityData("Orders");

   return ReadResponse.setSuccess().setData(ex).response();
}
  1. A method to validate incoming order requests (coming as HTTP Post via Postman in our case)
@BeforeCreate(entity = "Orders", serviceName = "CatalogService")
    public BeforeCreateResponse beforeCreateOrders(CreateRequest req, ExtensionHelper helper)
            throws DatasourceException {
        // EntityData data = req.getData();
        // //TODO: add your custom logic / validations here...
        //

        // Validation of order-amount:
        EntityData orderItem = req.getData();
        Integer amount = (Integer) orderItem.getElementValue("amount");
        if (amount == null || amount <= 0) {
            return getErrorResponse(400, "it's your fault: order at least one book");
        }

        Integer id = (Integer) orderItem.getElementValue("book_ID");
        EntityData stockBook = helper.getHandler().executeRead("Books", Collections.singletonMap("id", id),
                Arrays.asList("book_ID", "stock"));

        int stock = (Integer) stockBook.getElementValue("stock");

        // not enough books in stock
        if (stock < amount) {
            return getErrorResponse(409, "I'm sorry, you are too late. not anymore enough in stock");
        }

        return BeforeCreateResponse.setSuccess().response();

    }

(CAUTION: This method will not work 😉 Next step will be fixing this exception!)

00:22:30: But when we run the application and try to create an order (running through our recently created validation, see above) we face a server side issue with HTTP 500. So, looking at the logs in SAP Cloud Platform, we (or in this case it’s obviously me 🙂 ) gave the executeRead method the wrong database fields to select. book_ID is not part of the Books entity and so the SQL Select executed by CDS itself could not succeed and throws an exception. As we didn’t handle (via try/catch) the exception directly but added the trows clause to the method itself a so to say undetailed (HTTP 500) error got returned to the sender of the POST request. Changing it to the actual field name ID of Entity Books will fix this issue and our method is working as expected.

00:26:00: Right at the time when we wanted to restart the application with the fixed coding we have some issues to start the application again and also stopping the already running instance. So we have a look at the SAP Cloud Platform cockpit what’s going on with our application instance. To be able to start the application we force the existing instance to stop.

00:30:00: Going to test our fixed issue with a positive and negative test and as well having a look at the effected Entities!

00:34:00: Last but not least we need to update the Books Entity accordingly (or actually the stock attribute of the Entity) to reflect the recent order requests also there. And as I’m still challenged with live coding and commenting in parallel…I was somehow not able to get the implementation working for the said logic. But only for the meantime – we will come back to this piece of coding at the end of the episode 😉

00:47:00: We take a step back and look at how the actual Java project is set up. So far, we have only created the basic structure of the project using the SAP Cloud Platform WebIDE template (called SAP Cloud Platform Business Application) and have completely relied on what has been generated for us. For this reason, we look at the actual build process (done with maven), which artifacts are used and what happens in the background. The crucial file for this is called pom.xml (pom = project object model), which contains all information about the build process.

00:50:30: The pom.xml we see in our project is relying on a parent artifact:

<parent>
  <groupId>com.sap.cloud.servicesdk.prov</groupId>
  <artifactId>projects-parent-odatav2</artifactId>
  <version>1.28.1</version>
</parent>

This causes a lot of inheritance. Which is really helpful to have, but to properly understand what’s actually happening we take a look under the hood. mvn help:effective-pom executed on my local machine will resolve the inheritance and finally shows us the effective pom of our project.

00:57:30: Fixing the java coding where we left off before we had a look at maven. The final coding is:

@BeforeCreate(entity = "Orders", serviceName = "CatalogService")
public BeforeCreateResponse beforeCreateOrders(CreateRequest req, ExtensionHelper helper)
    throws DatasourceException {
  // EntityData data = req.getData();
  // //TODO: add your custom logic / validations here...
  //

  // Validation of order-amount:
  EntityData orderItem = req.getData();
  Integer amount = (Integer) orderItem.getElementValue("amount");
  if (amount == null || amount <= 0) {
    return getErrorResponse(400, "it's your fault: order at least one book");
  }

  Integer id = (Integer) orderItem.getElementValue("book_ID");
  EntityData stockBook = helper.getHandler().executeRead("Books", Collections.singletonMap("id", id),
      Arrays.asList("id", "stock"));

  int stock = (Integer) stockBook.getElementValue("stock");

  // not enough books in stock
  if (stock < amount) {
    return getErrorResponse(409, "I'm sorry, you are too late. not anymore enough in stock");
  }

  stock -= amount;
  EntityData updatedBook = new DefaultEntityData(Collections.singletonMap("stock", stock),
      ((HasMetadata) stockBook).getEntityMetadata());
  helper.getHandler().executeUpdate(updatedBook, Collections.singletonMap("ID", id), false);

  return BeforeCreateResponse.setSuccess().response();

}

private static BeforeCreateResponse getErrorResponse(int statusCode, String msg) {
  return BeforeCreateResponse
      .setError(ErrorResponse.getBuilder().setStatusCode(statusCode).setMessage(msg).response());
}
Be the first to leave a comment
You must be Logged on to comment or reply to a post.