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

Introduction

During research, development, and testing of applications, we often encounter small but interesting details that can consume a significant amount of time to discover and comprehend. These valuable insights are often forgotten or overlooked when stored in isolated notes, resulting in the need to repeat the same research in the future.

To address this issue, I am starting an ongoing blog series called ‘Quirky Nuggets’ with this blog post, dedicated to capturing and sharing these overlooked or buried details related to CAP (Cloud Application Programming) or BTP (Business Technology Platform) in general. Each blog post within this series will represent a new learning or discovery, allowing me to continually expand and update the collection as I encounter new and different insights.

Now, let’s delve into a realm of knowledge nuggets and explore various insights and discoveries.

Nugget 01

To provide some context for beginners in CAP and to refresh the concepts for those who have already explored it, the CAP runtime offers a set of standard functionalities for services. These include CRUD operations, deep read, insert, update, and delete, as well as text data search, pagination and sorting, and input validation through annotations. However, in many cases, we need to use custom handlers to incorporate specific logic to meet business process requirements. These custom handlers are sometimes referred to as event handlers, as even synchronous APIs are treated as events within the CAP runtime.

In essence, event handlers are methods (or functions in programming languages) that are registered to be executed when a particular event occurs. The event can be a CRUD operation on an entity, such as reading books or inserting authors. It can also be a function or action defined as part of the service definition. The CAP framework allows event handlers to be registered at different phases or stages of event processing, namely:

  • “On” handlers: These handlers run instead of the generic/default handlers.
  • “Before” handlers: These handlers run before the “on” handlers.
  • “After” handlers: These handlers run after the “on” handlers and receive the result as input.

Now, let’s examine an example of an “after” handler, as this particular piece of information focuses on it.

Business Requirement: Add ‘free delivery on Pro Membership’ text in book description if category of the book is ‘Fiction’.

const cds = require("@sap/cds");
module.exports = cds.service.impl(async function (srv) {

  const {Books} = this.entities;
  srv.after('READ', Books, async (books, req)=>{
    books.forEach(book => {
      if(book.category == 'Fiction'){
        book.description = book.description + ' [free delivery on Pro Membership]';
      }
    });
  })

})

Explanation:

  1. after(‘READ’, Books, async (books, req)=>{ … }) : This line registers a function to be executed after a “READ” operation is performed on the Books entity. It takes a callback function that receives the books (result of on phase i.e. Books Data) and req (request) parameters.
  2. forEach(book => { … }) : This line starts a loop that iterates over each book
  3. if(book.category == ‘Fiction’){ … } : This line checks if the category of book is ‘Fiction’.
  4. description = book.description + ‘ [free delivery on Pro Membership]’ : this line modifies the description of book.

So this after event handler modifies the book data and returns the result. Now Let’s look at another example for a function.

Service Definition 

Service Definition
service MyService {
  type greetMsg { value:String(50) };

  function funcHello(name: String(20)) returns String(50);
  function funcGreet(name: String(20)) returns greetMsg;
}
Service Implementation: funcHello 

srv.on('funcHello', async (req) => {
  return `Hello ${req.data.name}`;
});

srv.after('funcHello', async (output, req) => {
  output = `Hello, ${req.data.name} !!!`;
});

### Request 01: funcHello – Returns Output of String Type

GET http://localhost:4004/my/funcHello(name=’Ajit’)

Service Implementation: funcGreet 

srv.on('funcGreet', async (req) => {
  return {value: `Hello ${req.data.name}`};
});

srv.after('funcGreet', async (output, req) => {
  output.value = `Hello, ${req.data.name} !!!`;
});

### Request 02: funcGreet – Returns Output of Object Type which has a ‘value’ property

GET http://localhost:4004/my/funcGreet(name=’Ajit’)

Now let’s look at the result. The result is different. In one of event handler, changes are appeared in result and other one changes are ignored. This is simply because of the type of the return results. In case of Request 01, return type is string where result did not change based on the login in after event handler. However, for Request 02, return type is object where result has changed. So if you make change to an object then changes will appear in result of the after phase event handler.

Nugget 01 Summary:

Result of ‘on’ phase is available as first parameter in ‘after’ phase of any event like READ, CREATE, UPDATE, functions, actions etc. Result is passed by value. Hence when the type of result is primitive like String, or Integer, etc change of result in after phase has no  impact. But if type of result is object, or array (which are basically referenced in javascript), then change of result in after phase will return the changed result.
So, to avoid confusion, personally I would recommend to use always objects or arrays as returns of functions and actions. This is not a concern for CRUD operations as the return type by default is alwaysg an object or an array.

Nugget 02

This quirky nugget is about annotation @assert.unique used for validating input data uniqueness when creating or updating entries in a database. Also, This annotation can only be used for entities which results in table in SQL Databases. Essentially it creates a unique constraint for the entity in database. Let’s look at a quick example:

db/schema.cds cds compile db/schema.cds –to sql
namespace cap_quirks_02.db;

@assert.unique: 
{uniqueroots: [name, category]}
entity Roots {
  key id: Integer;
  name: String(30);
  descr: String(100);
  category: String(10);
}
CREATE TABLE cap_quirks_02_db_Roots (
  id INTEGER NOT NULL,
  name NVARCHAR(30),
  descr NVARCHAR(100),
  category NVARCHAR(10),
  PRIMARY KEY(id),
  CONSTRAINT 
    cap_quirks_02_db_Roots_uniqueroots 
    UNIQUE (name, category)
);

So if you look at the above example, in schema.cds, data uniqueness is defined for combination of name and category using the @assert.unique annotation. This results in a constraint for the table in db as shown above.

Note that uniqueness is defined using the column names. However, This gets tricky when nested properties are used in schema definition. Now let’s enhance the schema mentioned earlier with some nested properties and also see the compiles sql of it.

db/schema.cds with assignment nested property cds compile db/schema.cds –to sql
entity Roots {
    key id: Integer;
    name: String(30);
    descr: String(100);
    category: String(10);
    assignment:{
        type: String(10);
        info: String(100)
    }
}
CREATE TABLE cap_quirks_02_db_Roots (
  id INTEGER NOT NULL,
  name NVARCHAR(30),
  descr NVARCHAR(100),
  category NVARCHAR(10),
  assignment_type NVARCHAR(10),
  assignment_info NVARCHAR(100),
  PRIMARY KEY(id)
);

Now if we have to define data uniqueness for category and type of assignment, then our initial instinct would be to define using column names that was generated in compilation as below:

@assert.unique: {uniqueroots: [category, assignment_type]}

However, this does not work. And gives error: [ERROR] db/schema.cds:4:8: “@assert.unique.uniqueroots”: “assignment_type” has not been found (in entity:”cap_quirks_02.db.Roots”)

Upon some exploration, found that dot notation should be used to define uniqueness as shown below:

@assert.unique: {uniqueroots: [category, assignment.type]}

Nugget 02 Summary:

In case of nested properties in schema, dot notation is used to define the uniqueness with @assert.unique annotation.

Conclusion:

In this blog post, we explored a bit about event handler and handling data uniquness via annotation.

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.

Sara Sampaio

Sara Sampaio

Author Since: March 10, 2022

0 0 votes
Article Rating
Subscribe
Notify of
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x