Skip to Content
Technical Articles

Developing Business Services with Cloud Application Programming(CAP) Model in SAP Business Studio

MOTIVATION

In this blog we will see about SAP Cloud Application Programming Model (CAPM), developing a business services using CAP model , inserting data in the business services (one to many and many to many associations), adding user authentication and authorization while accessing CDS services.

Introduction

SAP CAP is a frameworks of tools, languages and libraries designed to achieve CRUD operation on business data efficiently with SAP best practices and to help developers to minimize coding efforts, develop reusable peace of codes in form of micro services.

Below image is the understanding the flow of development in terms of CAP development

 

Here is the link to know more about the SAP CAP model design principles and development tools.

Building Service using NodeJS:

After creating a account in the SAP Cloud Platform Trail, open the SAP Business Application studio for the development.

Create a project using the command cds init bank-operations in the terminal and install all the necessary node modules using npm install .

Defining the Service :

Create a schema.cds file in the db folder and add the below CDL ( extension of SQL) code.

namespace sap.capire.bank_details;

using { Currency, managed, cuid, temporal } from '@sap/cds/common';

type Acc_type : String enum {
  Savings;
  Current;
}

type Status : String enum {
    Active;
    DeActivated;
}

type Transaction_type : String enum {
    deposit;
    withdraw;
}

entity State  {
    key ID : Integer;
    name : localized String(100) not null @assert.unique;
    customers : Composition of many Customers on customers.state = $self;
    banks : Composition of many Banks on banks.state = $self;
}

entity City  {
    key ID : Integer;
    name : localized String(100) not null @assert.unique;
    customers : Composition of many Customers on customers.city = $self;
    banks : Composition of many Banks on banks.city = $self;
}

entity Banks :  managed {
    key bankID : Integer  @assert.unique;
    bankname : String(100) @assert.unique;
    state : Composition of State ;
    city : Composition of City;
    status : Status default 'Active';
    customers : Composition of many Customers on customers.bank = $self;
}

entity Customers :   managed {
    key custID : Integer  @assert.unique;
    firstname : localized String(50) ;
    lastname : localized String(50) ;
    age : Integer ;
    dateOfBirth  : Date;
    bank : Composition of Banks;
    address : localized String(500) ;
    state : Composition of State ;
    city : Composition of City;
    status : Status default 'Active';
    message : String(50) default 'Customer Created Successfully';
    accounts : Composition of many Accounts on accounts.customers = $self;
}

entity Accounts : managed {
    key accountid : Integer64 @assert.unique;
    customers : Association to  Customers;
    account_type : Acc_type default 'Savings';
    Balance : Integer;
    account_status : Status default 'Active';
    message : String(50) default 'Account Created Successfully';
    transections : Composition of  many Transections on transections.accounts = $self;
}

entity Transections : cuid {
    key accounts : Association to  Accounts;
    type : Transaction_type;
    description : String(100);
    date : DateTime @cds.on.update: $now;
    amount : Integer;
}

Execute cds watch from the Terminal to see the output.

user: bank_operations $ cds watch

Adding the data model and adapt the service definition using SAP CDS : 

We can define the authorization in the CAP CDS model  using two annotations,

  1. @restrict – Restrict the privileges in entity levels
  2. @requires – Restrict the privileges in service levels

Create a service file managerservice.cds in the srv folder

using { sap.capire.bank_details as my } from '../db/schema';

service ExecutiveService @(requires: 'authenticated-user' ) {

    entity Banks @(restrict: [ { grant: '*', to: 'Manager'}])
    as projection on my.Banks {
        key bankID,
        bankname,
        state.name as StateName,
        city.name as CityName,
        state,
        city,
        customers
    };

    entity Customers @(restrict: [ { grant: '*', to: 'Manager'}])
    as projection on my.Customers  {
        key custID,
        firstname,
        lastname,
        bank.bankID as BankId,
        bank.bankname as BankName,
        status as Accountstatus,
        address,    
        city.name as CityName,
        state.name as StateName,
        accounts,
        state,
        city,
        bank
    };

    entity Accounts @(restrict: [ { grant: '*', to: 'Manager'}])
    as projection on my.Accounts {
        key accountid,
        account_type,
        account_status,
        Balance,    
        customers.custID,
        customers.firstname ,
        customers.lastname,
        customers.bank.bankID,
        customers.bank.bankname,
        message,
        createdAt,
        transections,
        customers
    } ;

    entity Transections @(restrict: [ { grant: '*', to: 'Manager'}])
    as projection on my.Transections;
    
    @readonly State as projection on my.State;
}

 

So from the above code we can understand that user should be a authenticated-user and should have the Manger role assigned to them to access the data in the business service.

Creating Authorization Roles and adding Mock data:

Create users and assign the role to them in the .cdsrc.json file

{
    "auth": {
        "passport": {
            "strategy": "mock",
            "users": {
                "Ranjith": {
                    "password": "user",
                    "ID": "200",
                    "roles": [
                        "authenticated-user",   
                        "Manager"
                    ]
                },
                "Vinyan": {
                    "password": "user",
                    "ID": "202",
                    "roles": [
                        "authenticated-user",
                        "customer"
                    ]
                }
            }
        }
    }
}

 

Create a data folder inside the database(db) and add the mock data as a .csv file

Add sap.capire.bank_details-State.csv file in the data folder.

ID;name
401;"Tamilnadu"
402;"Kerala"

 

Again run CDS watch and can observe the service output in new tab.

When clicking any entity, you can see the browser is asking for the user authentication details. Provide the valid user details to access the data.

Adding Custom logic:

We can add our custom logic by registering the event handlers using

  1. srv.on (event, entity?, handler) 
  2. srv.before (event, entity?, handler) 
  3. srv.after (event, entity?, handler) 

Lets create a manager-service.js to add our custom logic

const cds = require('@sap/cds')
const { Banks, Customers, Accounts, Transactions } = cds.entities

module.exports = cds.service.impl((srv)=> {
    srv.before('CREATE', 'Transections', _transection)
    //srv.before('UPDATE', 'Transections' , _beforeTransectionUpdate)
    srv.after('CREATE',  'Banks', _afterCreationBank)
})

 async function _transection(req) {
    const data = req.data
    console.log(req.user)
    return cds.transaction(req).run(()=> { 
        if(data.hasOwnProperty("type")) {
            if(data.type === 'Deposit') {
                UPDATE(Accounts).set('balance +=', data.amount).where('accountid =', data.accounts_accountid)
            } else {
                UPDATE(Accounts).set('balance -=', data.amount).where('accountid =', data.accounts_accountid).and(
                    'balance >=', data.amount
                )
            }
        }
    }).then((affectedrows) => {
        if(affectedrows == 0) {
            req.error(409, `insufficient balance in the #${data.accounts_accountid}`)
        }
    })
}

 async function _afterCreationBank(bankdetails, req) {
    console.log(`Bank ${bankdetails.bankID} is Created`)
    for(let i in bankdetails.customers) {
        console.log(`Customer ${bankdetails.customers[i].custID} is Created`)
        for(let j in bankdetails.customers[i].accounts) {
            console.log(`Account ${bankdetails.customers[i].accounts[j].accountid} is Created`)
            for(let k in bankdetails.customers[i].accounts[j].transections) {
                console.log(`Transecction ${bankdetails.customers[i].accounts[j].transections[k].ID} is Created`)
            }
        }
    }
}

 

Note : We can also use @path, @impl annotation to define the custom logic files.

CRUD Operation:

Can perform CRUD operation by creating .http file in the project

create manager.http file to perform the CRUD operation:

### creating all the details using many to many association

### Read 

send Request
GET http://localhost:4004/executive/Banks HTTP/1.1\
Authorization: Basic YWRtaW46

### Create

send Request
POST http://localhost:4004/executive/Banks HTTP/1.1
Content-Type: application/json
Authorization: Basic YWRtaW46


{ 
    "bankID": 117,
    "bankname": "Mahendra Bank",
    "state_ID" : 401,
    "city_ID" : 502,
    "customers" : [
        {
        "custID" : 230,
        "firstname" : "Dhnees",
        "lastname" : "naaraj",
        "address" : "asff Street",
        "state_ID" : 401,
        "city_ID" : 502,
        "accounts" : [            
            {
                "accountid" : 1000000000000800,
                "Balance" : 45,
                "account_status" : "Active",
                "account_type" : "Savings",
                "transections" : [
                    {
                        "type" : "Deposit",
                        "description" : "Check Deposit",
                        "amount" : 45
                    }
                ]
            },
            {
                "accountid" : 1000000000000900,
                "Balance" : 45,
                "account_status" : "Active",
                "account_type" : "Current",
                "transections" : [
                    {
                        "type" : "withdraw",
                        "description" : "Check Deposit",
                        "amount" : 45
                    }
                ]
            }
        ]
        },
        {
        "custID" : 231,
        "firstname" : "Dhaneesha",
        "lastname" : "narayanaraj",
        "address" : "Kuppaiah Street",
        "state_ID" : 401,
        "city_ID" : 502,
        "accounts" : [            
            {
                "accountid" : 1000000000001100,
                "Balance" : 45,
                "account_status" : "Active",
                "account_type" : "Savings",
                "transections" : [
                    {
                        "type" : "Deposit",
                        "description" : "Check Deposit",
                        "amount" : 45
                    }
                ]
            },
            {
                "accountid" : 1000000000001200,
                "Balance" : 45,
                "account_status" : "Active",
                "account_type" : "Current",
                "transections" : [
                    {
                        "type" : "withdraw",
                        "description" : "Check Deposit",
                        "amount" : 45
                    }
                ]
            }
        ]
        }
    ] 
}

now you can refresh the browser and check the output.

Final project structure looks like below,

In my next blog i will share how to create SAP FIORI Elements using CDS Annotations, xs-security.json file for adding the role template and adding the role collection into the SAP Cloud Foundry .

Thank you 🙂

 

1 Comment
You must be Logged on to comment or reply to a post.