Introducing Event Handlers for Custom Logic

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

After completing this lesson, you will be able to:

  • Explain event handlers for custom logic
  • Run SQL in the database with Node.js

Introducing Service API for Custom Logic

Custom Code is the logic that you can add to the application to express things like input validations,additional calculations, call to other services, to the database and more.

We have 4 types of API's as seen in the picture,

  • Construct, Reflection API :

    This deals with constructing and looking things up in services or connecting to other required services. Not commonly used. Usually, you will not be confronted too much with these.

  • Querying API :

    This is a query API, through which you can send synchronous queries to services,including databases.

  • Messaging API :

    This is the asynchronous counterpart of the query API, with which services can send messages to one another.

  • Event Handling :

    These are used to register custom event handlers.

As we see on the picture,the box for services appears twice.

This shows that services can mean our own services that we provide as part of the application,or it denotes remote services from other applications. And third, it means in databases which we have the same API's for in the programming model.

For this unit, we are going to focus on the APIs around registering event handlers(4) to the runtime system.

Introducing Custom Event Handlers

Event Handler

In SAP Cloud Application Programming Model, everything that happens at runtime is an event that is sent to a service. They are a powerful means to extend CAP. An event handler is simply a method, that is executed when something happens in the application.

If you need a specific service to react to a specific event, you register an event handler using srv.<phase>(<event>), where

  • srv is the instance of the service that you are extending,
  • <phase> is one of on, before, or after (see section Event Phases) and
  • <event> is any kind of named event as a string (e.g. 'READ').

Once a service has an event handler for a specific event, it becomes a consumer for that event. Using srv.emit(<event>), a service can send arbitrary events. These events then get consumed by other services that have event handlers registered for the respective event.

Watch this video to learn about the custom event handler phases.

In the first example, code is added before Orders are created through a post or put request or so. For example, one might verify that there's enough stock or execute any check before the actual request is processed.

In a second example, the code is executed after books are read.

And in the third example, code is registered to run instead of the generic framework handler.

In the case of an external service, there wouldn't be anything the framework could do,so you must provide an on handler, but you can also use the on hook to override generic CRUD handlers. However, it is recommended to use a generic implementation and only diverge from it if you need different logic.

Also know that you can add more than one event handler for the same event, for example two before handlers for the same order creation event.

Likewise, a single handler can handle multiple events. For example, just omit the book string in the second example,to have a handle that is called after all entities are read, no matter they are Books, Authors, or Orders.

Request objects are passed in the event handlers , they provides all sorts of information about the context, like the request data or the method,like get post and so on.

Request objects are also used to provide error messages back to the client, or to register another set of handlers for the request lifecycle, like when the request has completed, succeeded, or failed.

capire - Core Services APIs (cloud.sap)

Introducing Registering Event Handlers

  • Option 1 :

    In this method the Javascript file is placed next to the CDS (.cds) file used to define the service, the Javascript file needs to have the same name as the .cds file, this way the framework hooks up the implementation to the service file.

  • Option 2 :

    Here we set the link through the impl annotation in your CDS model file (.cds), where the respective service implementation can be found. This is useful if you have diverging file names or you want to make it very explicit that the two files belong together.

  • Option 3 & 4 :

    These are fairly advanced, and used with the CDS serve API to bootstrap your services on your own.

  • Option 5 :

    Is used when dealing with external services.

Watch this video to learn about the handler registering options.

We will be using Option 1 for Registering the handlers as shown in the process flow

We have defined two Custom Handlers,

First one will get triggered after the products Entity has been read and the output will be shown in the console depending on the number of records selected.

Second one is Triggered when either a Create,Update or Delete is performed on the Products Entity. The Product ID with message will be displayed in the console.

As we cannot perform POST operations from the browser we will use a Rest Client, a utility available in SAP Business Application Studio.

Define Custom Handlers and Test

Code File- Define Custom Handlers and Test

We create the custom handler definition. We create a .jsfile under srv folder.

We create cat-service.js. Copy the below code.

Note
Please note we use the option 1 of Registering the Custom Handler events as mentioned in Unit 10 under Introducing Registering Event Handlers. Option 1 - Needs the .js file to have the same name as the .cds file defined for OData service. We used cat-service.cds so we need a cat-service.js

In the code below we observe two handlers,

  • First one provide a console log whenever the Products Entity is read.
  • Second one provides a console log when there is a Create, Update, Delete activity on the Products entity
Note
You can create similar handlers for Suppliers entity or add validations for user inputs.
Code snippet
const cds = require('@sap/cds')
module.exports = cds.service.impl(function () {
    const {Products} = this.entities()
    
    this.after('each',Products, row =>{
        console.log(`Read Product: ${row.ID}`)
    })
    
    this.after(['CREATE', 'UPDATE', 'DELETE'], [Products], async (Product, req) => {
        const header = req.data
        req.on('succeeded', () => {
            global.it || console.log(`< emitting: product_Changed ${Product.ID}`)
            this.emit('prod_Change', header)
        })
    })
})
Copy code

Tests

As the POST operations cannot be tested using browser, we have used the inbuilt REST client to perform this test. We create a .httpfile under tests folder.

We create Products.http. Copy the below code.

Code snippet
### Submit Suppliers
GET http://localhost:4005/catalog/Suppliers?$top=2

### Browse Products
GET http://localhost:4004/catalog/Products?$top=2

### Browse Products - Product,Identifier ,Price
GET http://localhost:4004/catalog/Products?
&$select=identifier,price

### Submit Products
POST http://localhost:4004/catalog/Products
Content-Type: application/json

{"ID":"a9fe4b01-ccd3-4369-821c-291a7552dffb" 
}
Copy code

Running SQL in the Database with Node.js

Process Web Requests with Express

Requests to a node service occur via the HTTP protocol. An application such as an SAPUI5 front-end can initiate these requests.

The node module receives the incoming HTTP request via the application router and executes a defined method, depending on the request method and registered application path.

The program can access the information sent in the HTTP request. For example, the parameters, and the request content.

During the server program execution, the HTTP response is prepared. It is then sent back to the requester once the program execution terminates.

Back on the client side, the requester can further process the HTTP response.

Connecting to the Database

To access SAP HANA database content, such as tables, procedures, and views, from a Node.js module, the @sap/hdbext module is used.

During the application deployment, the information in the Application Deployment Descriptor (mtad.yaml) is used to bind the database service instance to the Node.js module.

The binding information is available in the environment variables of the application. The module @sap/xsenv is used to retrieve the service instance from the environment and passed on to the module sap-hdbext.

The module, @sap/hdbext, sends the query to the database and provides a callback function to retrieve the results.

Consume XS Advanced Services

XS Advanced provides a convenient package (@sap/xsenv) for Node.js applications, which can be used to read bound services. To use the @sap/xsenv package, you have to add it to your application's dependencies, which are specified in a corresponding package.json file.

Created Service Bindings

Service bindings for an application can be retrieved, using the command XS ENV <APPLICATIONNAME>.

The attributes of a bound service can be used in a service query.

Connecting to the Database

@sap/hdbext is a small Node.js package, which extends the functionality of the hdb package. hdb is a JavaScript client for Node.js, which implements the SAP HANA database SQL command network protocol.

hdbext.middleware connects to SAP HANA automatically on each access to the specified path, - / in this case.

Afterwards the connection is available in req.db. This is the client object of SAP HANA database driver. The connection is closed automatically at the end of the request.

The result set is passed to the callback function and available in the rows array. In the example shown in the figure above, the first record is sent back to the client via the response object.

Function Implemented as Stored Procedure

We will see the process to Implement a CAP Function from for a Procedure.

  1. Add: function get_supplier_info() returns array of Suppliers; to the service definition. This will expose an OData Function as part of the service interface.
  2. Just adding the function doesn't do anything. We need to use the service handler exit in cat-service.js again to implement the call to the Stored Procedure. This logic will implement the exit handler for this function which in turn uses the standard @sap/hdbext module to call the Stored Procedure from HANA.
  3. We used two additional in our code, We need to add the two HANA modules (sap-hdbext-promisfied and @sap/hdbext) that we used in the code to our root package.json.
  4. As we updated the package.json we will run the npm install to install the dependencies
  5. Run cds build/all
  6. Run cds deploy
  7. Run cds watch
  8. The CAP preview UI doesn't list functions or actions, however. Just click on the /catalog link for the entire service.
  9. Manually add /get_supplier_info() to the end of the URL. If it works correctly it will show the output.

Function Implemented as Stored Procedure

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