9

Build E2E Application deployed and running on SAP ... - SAP Community

 7 months ago
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.
neoserver,ios ssh client

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
    hazem2020_1-1706316796349.png

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

    hazem2020_2-1706317781623.png

    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.
    hazem2020_3-1706318469125.png
  • 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) .
    hazem2020_6-1706318603727.png
  • Choose the entity.
  • Add the project attributes and be sure the deployment and FLP are checked .

    BAS3.png
  • Provide Deployment Configuration
    bas5.png
  • launchpad Configuration
    bas7.png
  • Start the Application.
  • bas9.png

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
    btpapp4-1.png
  • From Eclipse Create IAM app and add the created service from service tab
    hazem2020_10-1706319839797.png
  • Create a business catalog – and add the IAM app to it then publish locally
    hazem2020_11-1706319879988.png
  • Add the ui5 app ID to your IAM then activate and publish
    hazem2020_12-1706319925711.png
  •  Publish your business catalog
  •  From your cloud launchpad – open “Maintain Business Roles” app – and create new role
  •   Add the created business catalog
    BR-2.png
  • Assign your user
  • Change the restrictions to be unrestricted
  • BR -4.png
  • From “Manage launchpad space” create new space and page
    BR - 5.png
  • 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
    BR-7.png
  • Now you can test preview from "page preview"
  • The app is deployed, assigned to your user, and ready for use
    BR-8.png

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:

  • Developer Extensibility Documentation here
  • Developer Extensibility Overview here
  • Destination and Deployment here
  • Developing Extensions using ABAP Environment here

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK