0

Managed Scenario – The No Code / Low Code Approach

 1 year ago
source link: https://blogs.sap.com/2022/06/12/managed-scenario-the-no-code-low-code-approach/
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.

Managed Scenario – The No Code / Low Code Approach

In this blog lets look at the Managed Scenario (Behavior Implementation )  in the ABAP on BTP platform .

As mentioned in the title , Managed scenario goes hand in hand with the no / low code concept .  Since the managed approach takes all the responsibilities  of the CRUD operations , we just need to take account of certain authorizations (If we are going to implement the authorization objects and related objects ).

Lets consider the following scenario :

We have a student portal application with one header table and two other child tables .

Student Header Table :

@EndUserText.label : 'Header  for Student'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table ystudent_hdr {
  key client   : abap.clnt not null;
  key sid      : sysuuid_x16 not null;
  admission_no : abap.char(10);
  fname        : abap.char(25);
  mname        : abap.char(25);
  lname        : abap.char(25);
  contact_no1  : abap.char(10);
  contact_no2  : abap.char(10);
  dob          : abap.dats;
  gender       : ydgender;
  semail       : abap.char(50);
  pemail       : abap.char(50);
  paddress     : abap.string(256);
  caddress     : abap.string(256);
  blood_group  : ydbg;
  height       : abap.dec(4,4);
  weight       : abap.dec(4,4);

}

Student Academic details:

@EndUserText.label : 'Current Academic Details'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table yst_cracademic {
  key client   : abap.clnt not null;
  @AbapCatalog.foreignKey.screenCheck : false
  key sid      : sysuuid_x16 not null
    with foreign key ystudent_hdr
      where client = yst_cracademic.client
        and sid = yst_cracademic.sid;
  key course   : ydcourse not null;
  key semester : ydsemester not null;
  status       : ydsemstat;
  percentage   : abap.dec(3,2);
  feedback     : abap.string(256);

}

Student attendance details :

@EndUserText.label : 'Current Academic Details'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table yst_cracademic {
  key client   : abap.clnt not null;
  @AbapCatalog.foreignKey.screenCheck : false
  key sid      : sysuuid_x16 not null
    with foreign key ystudent_hdr
      where client = yst_cracademic.client
        and sid = yst_cracademic.sid;
  key course   : ydcourse not null;
  key semester : ydsemester not null;
  status       : ydsemstat;
  percentage   : abap.dec(3,2);
  feedback     : abap.string(256);

}

Following CDS entities representing the Parent child Hierarchy :

Header :

@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Student Header'
@Metadata.allowExtensions: true
define root view entity YSTUDENT_HDR_I
  as select from ystudent_hdr
  composition [0..*] of YST_CRACADEMIC_I as _academic
  composition [0..*] of YST_ATTENDANCE_I as _attendance
  association to YI_GENDER               as _gender on $projection.gender = _gender.Value
  association to YI_BG                   as _bg     on $projection.blood_group = _bg.Value
{
  key sid,
      admission_no,
      fname,
      mname,
      lname,
      contact_no1,
      contact_no2,
      dob,
      gender,
      _gender.Description as genderdesc,
      semail,
      pemail,
      paddress,
      caddress,
      blood_group,
      _bg.Description     as bg_desc,
      height,
      weight,
      _academic,
      _attendance,
      _gender,
      _bg
      //    _association_name // Make association public
}

Metadata Extension :

@Metadata.layer: #CORE
@UI: {
    headerInfo: { typeName: 'Student Detail',
                  typeNamePlural: 'Student Details',
                  
                  title: { type: #STANDARD, label: 'Student Details', value: 'fname' },
                  description:{ type: #STANDARD , label: 'Student Details' , value : 'lname'}

    },
    presentationVariant: [{ sortOrder: [{by: 'fname', direction: #ASC}]  }]


}
@Search.searchable: true
annotate view YSTUDENT_HDR_I with
{
  @UI.facet: [{ id: 'Student',
                  purpose: #STANDARD,
                  type: #IDENTIFICATION_REFERENCE,
                  label: 'Student Details',
                  position: 10
     },
     { id: 'Academic',
  purpose: #STANDARD,
  type: #LINEITEM_REFERENCE,
  label: 'Academic Details',
  position: 20,
  targetElement: '_academic'},
     { id: 'Attendance',
  purpose: #STANDARD,
  type: #LINEITEM_REFERENCE,
  label: 'Attendance Details',
  position: 20,
  targetElement: '_attendance'}]

  @UI: { identification: [{ position: 10, label: 'Student ID' }] }
  @UI.hidden: true
  sid;
  @UI: { lineItem:       [{ position: 20, importance: #HIGH, label: 'Admission Number' }],
         identification: [{ position: 20, label: 'Admission Number' }] }
  @Search.defaultSearchElement: true
  admission_no;
  @UI: { lineItem:       [{ position: 30, importance: #HIGH, label: 'First Name' }],
     identification: [{ position: 30, label: 'First Name' }] }
  @Search.defaultSearchElement: true
  fname;
  @UI: { lineItem:       [{ position: 40, importance: #HIGH, label: 'Middle Name' }],
     identification: [{ position: 40, label: 'Middle Name' }] }
  mname;
  @UI: { lineItem:       [{ position: 50, importance: #HIGH, label: 'Last Name' }],
     identification: [{ position: 50, label: 'Last Name' }] }
  @Search.defaultSearchElement: true
  lname;
  @UI: { lineItem:       [{ position: 60, importance: #HIGH, label: 'Contact Number 1' }],
     identification: [{ position: 60, label: 'Contact Number 1' }] }
  contact_no1;
  @UI: { lineItem:       [{ position: 70, importance: #HIGH, label: 'Contact Number 2' }],
     identification: [{ position: 70, label: 'Contact Number 2' }] }
  contact_no2;
  @UI: { lineItem:       [{ position: 80, importance: #HIGH, label: 'Date of Birth' }],
     identification: [{ position: 80, label: 'Date of Birth' }] }
  dob;
  @UI: { lineItem:       [{ position: 90, importance: #HIGH, label: 'Gender' }],
     identification: [{ position: 90, label: 'Gender' }] }
  @Consumption.valueHelpDefinition: [{ entity:
  {name: 'YI_GENDER' , element: 'Value' },
  additionalBinding: [{ localElement: 'genderdesc', element: 'Description' }]
  }]
  gender;
  @UI: { lineItem:       [{ position: 100, importance: #HIGH, label: '' }],
    identification: [{ position: 100, label: '' }] }
  genderdesc;
  @UI: { lineItem:       [{ position: 110, importance: #HIGH, label: 'Student Email' }],
     identification: [{ position: 110, label: 'Student Email' }] }
  semail;
  @UI: { lineItem:       [{ position: 120, importance: #HIGH, label: 'Parent Email' }],
     identification: [{ position: 120, label: 'Parent Email' }] }
  pemail;
  @UI: { lineItem:       [{ position: 130, importance: #HIGH, label: 'Parent Address' }],
     identification: [{ position: 130, label: 'Parent Address' }] }
  paddress;
  @UI: { lineItem:       [{ position: 140, importance: #HIGH, label: 'Student Address ' }],
      identification: [{ position: 140, label: 'Student Address ' }] }
  caddress;
  @UI: { lineItem:       [{ position: 150, importance: #HIGH, label: 'Blood Group' }],
     identification: [{ position: 150, label: 'Blood Group' }] }
  @Consumption.valueHelpDefinition: [{ entity:
  {name: 'YI_BG' , element: 'Value' },
  additionalBinding: [{ localElement: 'bg_desc', element: 'Description' }]
  }]
  blood_group;
  @UI: { lineItem:       [{ position: 160, importance: #HIGH, label: '' }],
     identification: [{ position: 160, label: '' }] }
  bg_desc;
  @UI: { lineItem:       [{ position: 170, importance: #HIGH, label: 'Height in CM' }],
  identification: [{ position: 170, label: 'Height in CM' }] }
  height;
  @UI: { lineItem:       [{ position: 180, importance: #HIGH, label: 'Weight in KG' }],
     identification: [{ position: 180, label: 'Weight in KG' }] }
  weight;

}

Academic details :

@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Academic Details'
@Metadata.allowExtensions: true
define view  entity  YST_CRACADEMIC_I
  as select from yst_cracademic
  association to parent YSTUDENT_HDR_I as _HDR      on $projection.sid = _HDR.sid
  association to YI_COURSE             as _course   on $projection.course = _course.Value
  association to YI_SEMESTER           as _semester on $projection.semester = _semester.Value
  association to YI_SEMSTAT           as _stat on $projection.status = _stat.Value
{
  key sid,
  key course,
  key semester,
      _course.Description   as course_desc,
      _semester.Description as semester_desc,
      status,
      _stat.Description as semester_status,
      percentage,
      feedback,
      _HDR // Make association public
}

Metadata extension:

@Metadata.layer: #CORE

@UI: {
    headerInfo: { typeName: 'Academic Detail',
                  typeNamePlural: 'Academic Details',
                
                  title: { type: #STANDARD, label: 'Academic Details', value: 'course' },
                  description:{ type: #STANDARD , label: 'Academic Details' , value : 'course_desc'}

    },
    presentationVariant: [{ sortOrder: [{by: 'semester', direction: #ASC}]  }]


}
@Search.searchable: true
annotate view YST_CRACADEMIC_I with
{
  @UI.facet: [{ id: 'Acadmic',
                  purpose: #STANDARD,
                  type: #IDENTIFICATION_REFERENCE,
                  label: 'Academic Details',
                  position: 10
     }]
  @UI: { identification: [{ position: 10, label: 'Student ID' }] }
  @UI.hidden: true
  sid;
  @UI: { lineItem:       [{ position: 20, importance: #HIGH, label: 'Course' }],
   identification: [{ position: 20, label: 'Course' }] }
  @Search.defaultSearchElement: true
  @Consumption.valueHelpDefinition: [{ entity:
  {name: 'YI_COURSE' , element: 'Value' },
  additionalBinding: [{ localElement: 'course_desc', element: 'Description' }]
  }]
  course;
  @UI: { lineItem:       [{ position: 30, importance: #HIGH, label: '' }],
      identification: [{ position: 30, label: '' }] }
  @Search.defaultSearchElement: true
  course_desc;
  @UI: { lineItem:       [{ position: 40, importance: #HIGH, label: 'Semester' }],
      identification: [{ position: 40, label: 'Semester' }] }
  @Search.defaultSearchElement: true
  @Consumption.valueHelpDefinition: [{ entity:
  {name: 'YI_SEMESTER' , element: 'Value' },
  additionalBinding: [{ localElement: 'semester_desc', element: 'Description' }]
  }]
  semester;
  @UI: { lineItem:       [{ position: 50, importance: #HIGH, label: '' }],
      identification: [{ position: 50, label: '' }] }
  @Search.defaultSearchElement: true
  semester_desc;
  @UI: { lineItem:       [{ position: 60, importance: #HIGH, label: 'Status' }],
  identification: [{ position: 60, label: 'Status' }] }
  @Search.defaultSearchElement: true
  @Consumption.valueHelpDefinition: [{ entity:
  {name: 'YI_SEMSTAT' , element: 'Value' },
  additionalBinding: [{ localElement: 'semester_status', element: 'Description' }]
  }]
  status;
  @UI: { lineItem:       [{ position: 70, importance: #HIGH, label: '' }],
   identification: [{ position: 70, label: '' }] }
  @Search.defaultSearchElement: true
  semester_status;
  @UI: { lineItem:       [{ position: 80, importance: #HIGH, label: 'Percentage Scored' }],
  identification: [{ position: 80, label: 'Percentage Scored' }] }
  @Search.defaultSearchElement: true
  percentage;
  @UI: { lineItem:       [{ position: 90, importance: #HIGH, label: 'Student Feed Back' }],
   identification: [{ position: 90, label: 'Student Feed Back' }] }
  @Search.defaultSearchElement: true
  feedback;

}

Attendance Details :

@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Attendance'
@Metadata.allowExtensions: true
define view entity YST_ATTENDANCE_I
  as select from yst_attendance
  association to parent YSTUDENT_HDR_I as _hdr      on $projection.sid = _hdr.sid
  association to YI_COURSE             as _course   on $projection.course = _course.Value
  association to YI_SEMESTER           as _semester on $projection.semester = _semester.Value
  association to YI_SEMSTAT            as _stat     on $projection.status = _stat.Value
{
  key sid,
  key course,
  key semester,
      _course.Description   as course_desc,
      _semester.Description as semester_desc,
      status,
      _stat.Description     as semester_status,
      percentage,
      feedback,
      _hdr // Make association public
}

Metadata Extension :

@Metadata.layer: #CORE
@UI: {
    headerInfo: { typeName: 'Attendance Detail',
                  typeNamePlural: 'Attendance Details',
                 
                  title: { type: #STANDARD, label: 'Attendance Detail', value: 'course' },
                  description:{ type: #STANDARD , label: 'Attendance Details' , value : 'course_desc'}

    },
    presentationVariant: [{ sortOrder: [{by: 'semester', direction: #ASC}]  }]


}
@Search.searchable: true
annotate view YST_ATTENDANCE_I
    with 
{
      @UI.facet: [{ id: 'Attendance',
                  purpose: #STANDARD,
                  type: #IDENTIFICATION_REFERENCE,
                  label: 'Attendance Details',
                  position: 10
     }]
  @UI: { identification: [{ position: 10, label: 'Student ID' }] }
  @UI.hidden: true
  sid;
  @UI: { lineItem:       [{ position: 20, importance: #HIGH, label: 'Course' }],
   identification: [{ position: 20, label: 'Course' }] }
  @Search.defaultSearchElement: true
  @Consumption.valueHelpDefinition: [{ entity:
  {name: 'YI_COURSE' , element: 'Value' },
  additionalBinding: [{ localElement: 'course_desc', element: 'Description' }]
  }]
  course;
  @UI: { lineItem:       [{ position: 30, importance: #HIGH, label: '' }],
      identification: [{ position: 30, label: '' }] }
  @Search.defaultSearchElement: true
  course_desc;
  @UI: { lineItem:       [{ position: 40, importance: #HIGH, label: 'Semester' }],
      identification: [{ position: 40, label: 'Semester' }] }
  @Search.defaultSearchElement: true
  @Consumption.valueHelpDefinition: [{ entity:
  {name: 'YI_SEMESTER' , element: 'Value' },
  additionalBinding: [{ localElement: 'semester_desc', element: 'Description' }]
  }]
  semester;
  @UI: { lineItem:       [{ position: 50, importance: #HIGH, label: '' }],
      identification: [{ position: 50, label: '' }] }
  @Search.defaultSearchElement: true
  semester_desc;
  @UI: { lineItem:       [{ position: 60, importance: #HIGH, label: 'Status' }],
  identification: [{ position: 60, label: 'Status' }] }
  @Search.defaultSearchElement: true
  @Consumption.valueHelpDefinition: [{ entity:
  {name: 'YI_SEMSTAT' , element: 'Value' },
  additionalBinding: [{ localElement: 'semester_status', element: 'Description' }]
  }]
  status;
  @UI: { lineItem:       [{ position: 70, importance: #HIGH, label: '' }],
   identification: [{ position: 70, label: '' }] }
  @Search.defaultSearchElement: true
  semester_status;
  @UI: { lineItem:       [{ position: 80, importance: #HIGH, label: 'Percentage Scored' }],
  identification: [{ position: 80, label: 'Percentage Scored' }] }
  @Search.defaultSearchElement: true
  percentage;
  @UI: { lineItem:       [{ position: 90, importance: #HIGH, label: 'Student Feed Back' }],
   identification: [{ position: 90, label: 'Student Feed Back' }] }
  @Search.defaultSearchElement: true
  feedback;
    
}

Few other CDS which is used for Search Help :

Blood Group:

@AbapCatalog.sqlViewName: 'YIBG'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Gender'
define view YI_BG as select from DDCDS_CUSTOMER_DOMAIN_VALUE_T( p_domain_name: 'YBG' )
{
key domain_name,
  key value_position,
      @Semantics.language: true
  key language,
      value_low as Value,
      @Semantics.text: true
      text      as Description
    
}

Domain :

YBG.png

Course :

@AbapCatalog.sqlViewName: 'YICOURSE'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Course'
define view YI_COURSE
  as select from DDCDS_CUSTOMER_DOMAIN_VALUE_T( p_domain_name: 'YCOURSE' )
{
  key domain_name,
  key value_position,
      @Semantics.language: true
  key language,
      value_low as Value,
      @Semantics.text: true
      text      as Description

}

Domain :

Y-course.png

Gender :

@AbapCatalog.sqlViewName: 'YGEN'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Gender'
define view YI_GENDER as select from DDCDS_CUSTOMER_DOMAIN_VALUE_T( p_domain_name: 'YGENDER' )
{
key domain_name,
  key value_position,
      @Semantics.language: true
  key language,
      value_low as Value,
      @Semantics.text: true
      text      as Description
    
}

Domain :

Ygender.png

Semester:

@AbapCatalog.sqlViewName: 'YISEM'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Semester'
define view YI_SEMESTER
  as select from DDCDS_CUSTOMER_DOMAIN_VALUE_T( p_domain_name: 'YSEMESTER' )
{
  key domain_name,
  key value_position,
      @Semantics.language: true
  key language,
      value_low as Value,
      @Semantics.text: true
      text      as Description

}

Domain :

YSEMESTER.png

Status :

@AbapCatalog.sqlViewName: 'YISTAT'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Status'
define view YI_SEMSTAT
  as select from DDCDS_CUSTOMER_DOMAIN_VALUE_T( p_domain_name: 'YSEMSTAT' )
{
  key domain_name,
  key value_position,
      @Semantics.language: true
  key language,
      value_low as Value,
      @Semantics.text: true
      text      as Description

}

Domain :

Ystat.png

Behavior definition :

managed implementation in class YST_BEHAVIOUR unique;
strict;
define behavior for YSTUDENT_HDR_I 
persistent table ystudent_hdr
lock master
authorization master ( instance )

{
  field ( numbering : managed, readonly ) sid;
  create;
  update;
  delete;
  field ( readonly ) genderdesc;
  field ( readonly ) bg_desc;
  association _attendance { create; }
  association _academic { create; }
}

define behavior for YST_ATTENDANCE_I 
persistent table yst_attendance
lock dependent by _hdr
authorization dependent by _hdr

{
  update;
  delete;
  field ( readonly ) sid;
  field ( readonly ) course_desc;
  field ( readonly ) semester_desc;
  field ( readonly ) semester_status;
  association _hdr;
}

define behavior for YST_CRACADEMIC_I 
persistent table yst_cracademic
lock dependent by _HDR
authorization dependent by _HDR

{
  update;
  delete;
  field ( readonly ) sid;
  field ( readonly ) course_desc;
  field ( readonly ) semester_desc;
  field ( readonly ) semester_status;
  association _HDR;
}

Behavior Implementation :

****Global Class ************
CLASS yst_behaviour DEFINITION
  PUBLIC
  abstract
  final
  for behavior of YSTUDENT_HDR_I .

  PUBLIC SECTION.
  PROTECTED SECTION.
  PRIVATE SECTION.
ENDCLASS.



CLASS yst_behaviour IMPLEMENTATION.
ENDCLASS.
****Local Types******

CLASS YST_behaviour2 DEFINITION INHERITING FROM cl_abap_behavior_handler.
  PRIVATE SECTION.

    METHODS get_instance_authorizations FOR INSTANCE AUTHORIZATION
      IMPORTING keys REQUEST requested_authorizations FOR ystudent_hdr_i RESULT result.


ENDCLASS.

CLASS YST_behaviour2 IMPLEMENTATION.
  METHOD get_instance_authorizations.
********Your code goes here
  ENDMETHOD.
ENDCLASS.

If we look at the Behavior class over here we have barely written 28 lines of code , if we expose the CDS in a service Definition and create a service Binding , All the CRUD operations for both parent and child works fine . All the CRUD operation code is taken care by the system and we have a minimum effort .

Since SAP recommends using the View Entities in CDS we had to create the class , if we were using just the views the class is optional.

Lets Have a look At the Screen:

Z.png
Z2.png

NB: The blog is written based on my Research and developments on BTP , If I have mentioned any miss information , Please correct me in the comments .


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK