Build E2E Application deployed and running on SAP ... - SAP Community
source link: https://community.sap.com/t5/technology-blogs-by-members/build-e2e-application-deployed-and-running-on-sap-s4-hana-cloud-public/ba-p/13582972
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Overview:
This blog is a detailed technical guide for the use of the Developer Extensibility option to develop an E2E Application deployed and running on SAP Public Cloud launchpad with the help of the Restful Application Programming Model (RAP).
Case Background:
Create a custom Fiori application to save product data, the app would simply do the following:
- The CRUD Operations
- Calculate the total amount for the product (QTY ** Price)
- Validate Price and Quantity
- Set the product to be “Ready for Sale”.
We will go through some stages to have the final result:
- Develop the backend service.
- Establish the destination between the Backend and BTP
- Consume the service in a Fiori element template using Business Application Studio.
- Deploy the application to the public cloud launchpad.
Detailed Steps:
1- Develop Backend Service:
- Create a database table
@EndUserText.label : 'Products Table'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #ALLOWED
define table zproducts {
key client : abap.clnt not null;
key product_uuid : sysuuid_x16 not null;
product_name : abap.char(40);
product_description : abap.char(120);
@Semantics.amount.currencyCode : 'zproducts.product_currency'
product_price : abap.curr(23,2);
product_currency : abap.cuky;
@Semantics.quantity.unitOfMeasure : 'zproducts.product_uom'
product_qty : abap.quan(23,2);
product_uom : meins;
@Semantics.amount.currencyCode : 'zproducts.product_currency'
total_amount : abap.curr(23,2);
ready_for_sale : abap_boolean;
last_changed_at : timestampl;
local_last_changed_at : timestampl;
}
- Create an interface CDS view on top of the DB table
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Products Interface View'
define root view entity zi_products
as select from zproducts
{
key product_uuid as ProductUuid,
product_name as ProductName,
product_description as ProductDescription,
product_price as ProductPrice,
product_currency as ProductCurrency,
product_qty as ProductQuantity,
product_uom as ProductUOM,
total_amount as TotalAmount,
ready_for_sale as ReadyForSale,
@Semantics.systemDateTime.lastChangedAt: true
last_changed_at as LastChangedAt,
@Semantics.systemDateTime.localInstanceLastChangedAt: true
local_last_changed_at as LocalLastChangedAt
}
- Then we create a consumption view.
@EndUserText.label: 'Products Consumption View'
@AccessControl.authorizationCheck: #NOT_REQUIRED
@Search.searchable: true
@Metadata.allowExtensions: true
define root view entity zc_products
provider contract transactional_query
as projection on zi_products as Products
{
@EndUserText.label: 'Product ID'
key ProductUuid,
@Search.defaultSearchElement: true
@EndUserText.label: 'Product Name'
ProductName,
@EndUserText.label: 'Product Description'
ProductDescription,
@EndUserText.label: 'Product Price'
ProductPrice,
@EndUserText.label: 'Product Currency'
ProductCurrency,
@EndUserText.label: 'Product Quantity'
ProductQuantity,
@EndUserText.label: 'Product UOM'
ProductUOM,
@EndUserText.label: 'Total Amount'
TotalAmount,
@EndUserText.label: 'Product is Ready for Sale'
ReadyForSale,
LastChangedAt,
LocalLastChangedAt
}
- A metadata extension was created to adapt the application's UI
@Metadata.layer: #CORE
@UI:{ headerInfo:{ typeName:'Products',
typeNamePlural:'Products',
title:{ type: #STANDARD , label: 'Products' } } }
annotate view zc_products with
{
@UI.facet: [{ id:'Products' , purpose: #STANDARD , type: #IDENTIFICATION_REFERENCE , label: 'Product Details', position: 10 }]
@UI:{ lineItem: [{ position: 1 }, { type: #FOR_ACTION,
dataAction: 'setToReadyForSale' ,
label: 'Ready For Sale' , invocationGrouping: #CHANGE_SET } ] ,
identification: [ { position: 1 } ] }
ProductUuid;
@UI: { lineItem: [ { position: 2 } ],
identification: [ { position: 2 } ],
selectionField: [ { position: 2 } ] }
ProductName;
@UI: { lineItem: [ { position: 3 } ],
identification: [ { position: 3 } ],
selectionField: [ { position: 3 } ] }
ProductDescription;
@UI: { lineItem: [ { position: 4 } ],
identification: [ { position: 4 } ] }
ProductPrice;
@UI: { lineItem: [ { position: 5 } ],
identification: [ { position: 5 } ] }
ProductCurrency;
@UI: { lineItem: [ { position: 6 } ],
identification: [ { position: 6 } ] }
ProductQuantity;
@UI: { lineItem: [ { position: 7 } ],
identification: [ { position: 7 } ] }
ProductUOM;
@UI: { lineItem: [ { position: 8 } ],
identification: [ { position: 8 } ] }
TotalAmount;
@UI: { lineItem: [ { position: 9 } ],
identification: [ { position: 9 } ] ,
selectionField: [ { position: 9 } ] }
ReadyForSale;
@UI.hidden: true
LastChangedAt;
@UI.hidden: true
LocalLastChangedAt;
}
- Create a behavior definition for the interface view with all the required operations.
managed implementation in class zbp_i_products unique;
strict ( 2 );
define behavior for zi_products alias products
with additional save
persistent table zproducts
lock master
authorization master ( global )
etag master LocalLastChangedAt
{
field ( readonly ) ReadyForSale, TotalAmount, LastChangedAt, LocalLastChangedAt;
field ( numbering : managed , readonly ) ProductUuid;
create;
update;
delete;
action ( features : instance ) setToReadyForSale result [1] $self;
determination calcTotalAmount on save { field ProductPrice , ProductQuantity ; }
validation validatePrice on save { field ProductPrice; create; }
validation validateQTY on save { field ProductQuantity; create; }
mapping for zproducts
{
ProductUuid = product_uuid;
ProductName = product_name;
ProductDescription = product_description;
ProductPrice = product_price;
ProductCurrency = product_currency;
ProductQuantity = product_qty;
ProductUOM = product_uom;
TotalAmount = total_amount;
ReadyForSale = ready_for_sale;
LastChangedAt = last_changed_at;
LocalLastChangedAt = local_last_changed_at;
}
}
- Create a behavior definition for the consumption view.
projection;
strict ( 2 );
define behavior for zc_products alias Products
{
use create;
use update;
use delete;
use action setToReadyForSale;
}
- Implement the Logic in a behavior implementation class
CLASS lhc_products DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS get_instance_features FOR INSTANCE FEATURES
IMPORTING keys REQUEST requested_features FOR products RESULT result.
METHODS get_global_authorizations FOR GLOBAL AUTHORIZATION
IMPORTING REQUEST requested_authorizations FOR products RESULT result.
METHODS settoreadyforsale FOR MODIFY
IMPORTING keys FOR ACTION products~settoreadyforsale RESULT result.
METHODS calctotalamount FOR DETERMINE ON SAVE
IMPORTING keys FOR products~calctotalamount.
METHODS validateprice FOR VALIDATE ON SAVE
IMPORTING keys FOR products~validateprice.
METHODS validateqty FOR VALIDATE ON SAVE
IMPORTING keys FOR products~validateqty.
ENDCLASS.
CLASS lhc_products IMPLEMENTATION.
METHOD get_instance_features.
ENDMETHOD.
METHOD get_global_authorizations.
ENDMETHOD.
METHOD settoreadyforsale.
MODIFY ENTITIES OF zi_products IN LOCAL MODE
ENTITY products
UPDATE FIELDS ( readyforsale )
WITH VALUE #( FOR key IN keys
( %key = key-%key
readyforsale = 'X' ) )
FAILED failed
REPORTED reported .
ENDMETHOD.
METHOD calctotalamount.
READ ENTITIES OF zi_products IN LOCAL MODE
ENTITY products
FIELDS ( productprice productquantity ) WITH CORRESPONDING #( keys )
RESULT DATA(lt_products) .
LOOP AT lt_products INTO DATA(ls_prd) .
DATA(lv_result) = ls_prd-productprice * ls_prd-productquantity .
MODIFY ENTITIES OF zi_products IN LOCAL MODE
ENTITY products UPDATE
FIELDS ( totalamount ) WITH VALUE #( ( %key = ls_prd-%key
totalamount = lv_result ) ) .
ENDLOOP.
ENDMETHOD.
METHOD validateprice.
READ ENTITIES OF zi_products IN LOCAL MODE
ENTITY products
FIELDS ( productprice ) WITH CORRESPONDING #( keys )
RESULT DATA(lt_products) .
LOOP AT lt_products INTO DATA(ls_prd) .
IF ls_prd-productprice < 1 .
APPEND VALUE #( %tky = ls_prd-%tky ) TO failed-products .
ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD validateqty.
READ ENTITIES OF zi_products IN LOCAL MODE
ENTITY products
FIELDS ( productquantity ) WITH CORRESPONDING #( keys )
RESULT DATA(lt_products) .
LOOP AT lt_products INTO DATA(ls_prd) .
IF ls_prd-productquantity < 1 .
APPEND VALUE #( %tky = ls_prd-%tky ) TO failed-products .
ENDIF.
ENDLOOP.
ENDMETHOD.
ENDCLASS.
CLASS lsc_zi_products DEFINITION INHERITING FROM cl_abap_behavior_saver.
PROTECTED SECTION.
METHODS save_modified REDEFINITION.
METHODS cleanup_finalize REDEFINITION.
ENDCLASS.
CLASS lsc_zi_products IMPLEMENTATION.
METHOD save_modified.
ENDMETHOD.
METHOD cleanup_finalize.
ENDMETHOD.
ENDCLASS.
- Create a service definition exposing the consumption view
@EndUserText.label: 'Products Service definition'
define service ZD_products {
expose zc_products as Products;
}
- Create a service binding (V2 OData – UI Service)
- Application Previewing
2- The destination between the Backend and BTP:
- In the connectivity section, we open destinations.
- create a new destination according to the below Guidance provided by SAP.
Field Name
Value
<YOUR_SYSTEMS_ID>_SAML_ASSERTION
Description
SAML Assertion Destination to SAP S/4HANA Cloud system <YOUR_SYSTEMS_ID>
In the SAP S/4HANA Cloud system, navigate to the Communication Systems app and copy the Host Name from Own SAP Cloud System = Yes
and paste it with prefix https:// for example https://my12345-api.s4hana.ondemand.com.
Proxy Type
Internet
Authentication
SAMLAssertion
Audience
Enter the URL of your system and remove -api, for example https://my12345.s4hana.ondemand.com.
AuthnContextClassRef
urn:oasis:names:tc:SAML:2.0:ac:classes:PreviousSession
Select New Property and maintain the following Additional Properties and values.
Field Name
Value
HTML5.DynamicDestination
HTML5.Timeout
60000
WebIDEEnabled
WebIDEUsage
odata_abap,dev_abap
nameIDFormat
urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
3- Service consumption in BAS:
- From the services section open the instances and subscriptions and open BAS(we assume you subscribe to BAS)
- Create a new DEV space of the type “SAP FIORI” and then run it.
- Run and Open the dev space
- Create a new SAP Fiori App and choose the service we created before (It will appear automatically if the destination was created successfully) .
- Choose the entity.
- Add the project attributes and be sure the deployment and FLP are checked .
- Provide Deployment Configuration
- launchpad Configuration
- Start the Application.
4- Deployment to Public Cloud Launchpad:
- Choose Deploy from application information page – choose (Y) from the terminal
- The deployment is successful when you receive messages like this
- From Eclipse Create IAM app and add the created service from service tab
- Create a business catalog – and add the IAM app to it then publish locally
- Add the ui5 app ID to your IAM then activate and publish
- Publish your business catalog
- From your cloud launchpad – open “Maintain Business Roles” app – and create new role
- Add the created business catalog
- Assign your user
- Change the restrictions to be unrestricted
- From “Manage launchpad space” create new space and page
- Again open the role and assign the created space
- Go to maintain launchpad pages – open the created page – Edit – add description and choose the business catalog by clicking add
- Now you can test preview from "page preview"
- The app is deployed, assigned to your user, and ready for use
Conclusion:
At the end I hope this blog was helpful and detailed for the whole process of creating Cloud Application. In the coming blogs we will build more complex Application which is interacted with standard entities such as (Purchase order , Sales orders , etc...)
References:
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK