In this blog lets look at the Managed Scenario (Behavior Implementation ) in the ABAP on BTP platform .
As mentioned in the title , Managed scenario helps to eliminate most of the code (used for CRUD operations in the Implementation class). 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 : 'Semester Attendance'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table yst_attendance {
key client : abap.clnt not null;
@AbapCatalog.foreignKey.screenCheck : false
key sid : sysuuid_x16 not null
with foreign key ystudent_hdr
where client = yst_attendance.client
and sid = yst_attendance.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 :
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 :
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 :
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 :
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 :
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:
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 .