- Design / Prototype
- Create Dictionary Objects
- Core Data Services (CDS)
- Business Service
- Business Object Behavior
- Create a Fiori App
- Source (GitHub) for Data Model/Behaviour/Service
- Source (GitHub) for User Interface (Fiori Elements App)
Introduction
A business object (BO) is a common term to represent a real-world artifact in enterprise application development such as the Product, the Travel, or the SalesOrder.
When going to implement an application scenario based on business objects, we may distinguish between the external, consumer-related representation of a business object and the internal, provider-related perspective:
– The external perspective hides the intrinsic complexity of business objects.
– The internal perspective exposes the implementation details and the complexity of business objects.
From a formal point of view, a business object is characterized by
– a structure,
– a behavior and
– the corresponding runtime implementation.
Structure
All entities – except the root entity – that represent a node of the business object structure serve as a:
- Parent entity – if it represents a node in a business object’s structure that is directly connected to another node when moving towards the root.
- Child entity – if it represents a node in a business object’s structure that is directly connected to another node (parent node) when moving away from the root.
- Leaf entity – if it represents a node in a business object’s structure without any child nodes. A leaf entity is a CDS entity, which is the target of composition (a child entity node) but does not contain a composition definition.
Behavior Definition
To specify the business object’s behavior, the behavior definition as the corresponding development object is used. A business object behavior definition (behavior definition for short) is an ABAP Repository object that describes the behavior of a business object in the context of the ABAP RESTful application programming model. A behavior definition is defined using the Behavior Definition Language (BDL).
A behavior definition always refers to a CDS data model. As shown in the figure below, a behavior definition relies directly on the CDS root entity. One behavior definition refers exactly to one root entity and one CDS root entity has at most one behavior definition (a 0..1 cardinality), which also handles all included child entities that are included in the composition tree. The implementation of a behavior definition can be done in a single ABAP class (behavior pool) or can be split between an arbitrary set of ABAP classes (behavior pools). The application developer can assign any number of behavior pools to a behavior definition (1..N cardinality).
A behavior specifies the operations and field properties of an individual business object in the ABAP RESTful programming model. It includes a behavior characteristic and a set of operations for each entity of the business object’s composition tree.
Behavior Characteristic
Behavior characteristic is that part of the business object’s behavior that specifies general properties of an entity such as:
ETag
Draft handling
Feature control
Numbering
Authorization Control
Apart from draft capabilities, these characteristics can be defined for each entity separately.
Operations
Each entity of a business object can offer a set of operations. They can cause business data changes that are performed within a transactional life cycle of the business object. As depicted in the diagram above, these modified operations include the standard operations create(), update(), and delete() as well as lock implementations and application-specific operations with dedicated input and output structures which are called actions. Another kind of operation is the read operation: they do not change any business data in the context of a business object behavior. Read operations include read, read by association, and functions (that are similar to actions, however, without causing any side effects).
For more information, see
Create Operation
Update Operation
Delete Operation
Actions
Locking
Source Code @ ZINTLRA_I_PB_HEAD (Behavior Definition)
managed; // implementation in class zbp_intlra_i_pb_head unique;
with draft;
define behavior for ZINTLRA_I_PB_HEAD alias Contact
implementation in class ZBP_INTLRA_I_PB_HEAD unique
persistent table zintlra_pb_d_hdr
draft table zint_pb_d_hdrtmp
lock master total etag PbChangedAt
etag master PbChangedAt
{
create;
update;
delete;
association _item { create(features : instance); with draft; }
field(numbering : managed, readonly) PbUuid;
field(mandatory) PbTagCode,PbLastName,PbEmailId;
field(readonly) PbOwner, PbId, PbCreatedAt, PbChangedAt;
action(features : instance) setFavourite result [1] $self;
action(features : instance) removeFavourite result [1] $self;
determination setPhoneBookId on save { field PbId; create; }
validation validateEmail on save { field PbEmailId; create;update; }
draft determine action Prepare {
validation validateEmail;
validation PhoneNumber~validatePhone;
}
mapping for zintlra_pb_d_hdr{
PbUuid = pb_uuid;
PbOwner = pb_owner;
PbId = pb_id;
PbTagCode = pb_tag_code;
PbFirstName = pb_first_name;
PbLastName = pb_last_name;
PbCreatedAt = pb_created_at;
PbChangedAt = pb_changed_at;
PbEmailId = pb_email_id;
PbFavourite = pb_favourite;
}
}
define behavior for ZINTLRA_I_PB_ITEM alias PhoneNumber
implementation in class ZBP_INTLRA_I_PB_ITEM unique
persistent table zintlra_pb_d_itm
draft table zint_pb_d_itmtmp
lock dependent by _hdr
etag master PbChangedAt
{
update;
delete;
association _hdr{ with draft; }
field(numbering : managed, readonly) PbItemUuid;
field(mandatory) PbCategory, PbTelephone;
field(readonly) PbUuid, PbId, PbItemId, PbCreatedAt, PbChangedAt;
action(features : instance) setDefault result [1] $self;
internal action setNotDefault;
determination setPhoneBookItemId on save { field PbItemId; create; }
validation validatePhone on save { field PbTelephone; create;update; }
mapping for zintlra_pb_d_itm {
PbItemUuid = pb_item_uuid;
PbUuid = pb_uuid;
PbId = pb_id;
PbItemId = pb_item_id;
PbCategory = pb_category;
PbTelephone = pb_telephone;
PbCreatedAt = pb_created_at;
PbChangedAt = pb_changed_at;
PbDefault = pb_default;
}
}
Before activating above we will have to create and activate the implementation classes and draft tables. You can right click on class name and draft table name and select Quick Fix to implement the same
Behavior Implementation Classes
Source @ ZBP_INTLRA_I_PB_HEAD
CLASS zbp_intlra_i_pb_head DEFINITION PUBLIC ABSTRACT FINAL FOR BEHAVIOR OF zintlra_i_pb_head.
ENDCLASS.
CLASS zbp_intlra_i_pb_head IMPLEMENTATION.
ENDCLASS.
CLASS lhc_Contact DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
CONSTANTS: BEGIN OF lc_favourite,
yes TYPE boolean VALUE 'X',
no TYPE boolean VALUE '',
END of lc_favourite.
METHODS get_features FOR FEATURES
IMPORTING keys REQUEST requested_features FOR Contact RESULT result.
METHODS setFavourite FOR MODIFY
IMPORTING keys FOR ACTION contact~setfavourite RESULT result.
METHODS removeFavourite FOR MODIFY
IMPORTING keys FOR ACTION contact~removefavourite RESULT result.
METHODS setPhoneBookId FOR DETERMINE ON SAVE
IMPORTING keys FOR Contact~setPhoneBookId.
METHODS validateEmail FOR VALIDATE ON SAVE
IMPORTING keys FOR Contact~validateEmail.
ENDCLASS.
CLASS lhc_Contact IMPLEMENTATION.
METHOD get_features.
" Fill the response table
READ ENTITIES OF zintlra_i_pb_head IN LOCAL MODE
ENTITY Contact
ALL FIELDS WITH CORRESPONDING #( keys )
RESULT DATA(Contacts).
result = VALUE #( FOR contact IN Contacts
LET
is_set_as_fav = COND #( WHEN contact-PbFavourite = lc_favourite-yes
THEN if_abap_behv=>fc-o-disabled
ELSE if_abap_behv=>fc-o-enabled )
is_not_set_as_fav = COND #( WHEN contact-PbFavourite = lc_favourite-no
THEN if_abap_behv=>fc-o-disabled
ELSE if_abap_behv=>fc-o-enabled )
IN
( %tky = contact-%tky
%action-setFavourite = is_set_as_fav
%action-removeFavourite = is_not_set_as_fav ) ).
ENDMETHOD.
METHOD setFavourite.
" Set as Favourite
MODIFY ENTITIES OF zintlra_i_pb_head IN LOCAL MODE
ENTITY Contact
UPDATE
FIELDS ( PbFavourite )
WITH VALUE #( FOR key IN keys
( %tky = key-%tky
PbFavourite = lc_favourite-yes ) )
FAILED failed
REPORTED reported.
" Fill the response table
READ ENTITIES OF zintlra_i_pb_head IN LOCAL MODE
ENTITY Contact
ALL FIELDS WITH CORRESPONDING #( keys )
RESULT DATA(Contacts).
result = VALUE #( FOR contact IN Contacts
( %tky = contact-%tky
%param = contact ) ).
ENDMETHOD.
METHOD removeFavourite.
" Set as Not a Favourite
MODIFY ENTITIES OF zintlra_i_pb_head IN LOCAL MODE
ENTITY Contact
UPDATE
FIELDS ( PbFavourite )
WITH VALUE #( FOR key IN keys
( %tky = key-%tky
PbFavourite = lc_favourite-no ) )
FAILED failed
REPORTED reported.
" Fill the response table
READ ENTITIES OF zintlra_i_pb_head IN LOCAL MODE
ENTITY Contact
ALL FIELDS WITH CORRESPONDING #( keys )
RESULT DATA(Contacts).
result = VALUE #( FOR contact IN Contacts
( %tky = contact-%tky
%param = contact ) ).
ENDMETHOD.
METHOD setPhoneBookId.
READ ENTITIES OF zintlra_i_pb_head IN LOCAL MODE
ENTITY Contact
ALL FIELDS WITH CORRESPONDING #( keys )
RESULT DATA(Contacts).
DELETE Contacts WHERE PbId IS NOT INITIAL.
CHECK Contacts IS NOT INITIAL.
LOOP AT Contacts ASSIGNING FIELD-SYMBOL(<lfs_contact>).
" Get Numbers
TRY.
cl_numberrange_runtime=>number_get(
EXPORTING
nr_range_nr = '01'
object = 'ZPBID'
IMPORTING
number = DATA(lv_number)
).
<lfs_contact>-PbId = lv_number.
CATCH cx_number_ranges INTO DATA(lx_number_ranges).
ENDTRY.
ENDLOOP.
MODIFY ENTITIES OF zintlra_i_pb_head IN LOCAL MODE
ENTITY Contact
UPDATE
FROM VALUE #( FOR contact IN Contacts INDEX INTO i (
%tky = contact-%tky
PbId = contact-PbID
%control-PbId = if_abap_behv=>mk-on ) )
REPORTED DATA(update_reported).
reported = CORRESPONDING #( DEEP update_reported ).
ENDMETHOD.
METHOD validateEmail.
DATA: go_regex TYPE REF TO cl_abap_regex,
go_matcher TYPE REF TO cl_abap_matcher,
go_match TYPE c LENGTH 1.
CREATE OBJECT go_regex
EXPORTING
pattern = '\w+(\.\w+)*@(\w+\.)+(\w{2,4})'
ignore_case = abap_true.
" Read E-Mail ID's
READ ENTITIES OF zintlra_i_pb_head IN LOCAL MODE
ENTITY Contact
FIELDS ( PbEmailId ) WITH CORRESPONDING #( keys )
RESULT DATA(Contacts).
LOOP AT Contacts INTO DATA(contact).
" Validate E-Mail
go_matcher = go_regex->create_matcher( text = contact-PbEmailId ).
APPEND VALUE #( %tky = contact-%tky %state_area = 'VALIDATE_EMAIL' ) TO reported-contact.
IF go_matcher->match( ) IS INITIAL.
" Invalid
APPEND VALUE #( %tky = contact-%tky ) TO failed-contact.
APPEND VALUE #( %tky = contact-%tky
%state_area = 'VALIDATE_EMAIL'
%msg = NEW zcm_intlra_pb(
severity = if_abap_behv_message=>severity-error
textid = zcm_intlra_pb=>invalid_email
email = contact-PbEmailId
)
%element-PbEmailId = if_abap_behv=>mk-on )
TO reported-contact.
ENDIF.
ENDLOOP.
ENDMETHOD.
ENDCLASS.
Source @ ZBP_INTLRA_I_PB_ITEM
CLASS zbp_intlra_i_pb_item DEFINITION PUBLIC ABSTRACT FINAL FOR BEHAVIOR OF zintlra_i_pb_head.
ENDCLASS.
CLASS zbp_intlra_i_pb_item IMPLEMENTATION.
ENDCLASS.
CLASS lhc_phonenumber DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
CONSTANTS: BEGIN OF lc_default,
yes TYPE boolean VALUE 'X',
no TYPE boolean VALUE ' ',
END of lc_default.
METHODS get_features FOR FEATURES
IMPORTING keys REQUEST requested_features FOR PhoneNumber RESULT result.
METHODS setDefault FOR MODIFY
IMPORTING keys FOR ACTION PhoneNumber~setDefault RESULT result.
METHODS setPhoneBookItemId FOR DETERMINE ON SAVE
IMPORTING keys FOR PhoneNumber~setPhoneBookItemId.
METHODS setNotDefault FOR MODIFY
IMPORTING keys FOR ACTION PhoneNumber~setNotDefault.
METHODS validatePhone FOR VALIDATE ON SAVE
IMPORTING keys FOR PhoneNumber~validatePhone.
ENDCLASS.
CLASS lhc_phonenumber IMPLEMENTATION.
METHOD get_features.
" Fill the response table
READ ENTITIES OF zintlra_i_pb_head IN LOCAL MODE
ENTITY PhoneNumber
ALL FIELDS WITH CORRESPONDING #( keys )
RESULT DATA(PhoneNumbers).
result = VALUE #( FOR Number IN PhoneNumbers
LET
is_set_as_default = COND #( WHEN Number-PbDefault = lc_default-yes
THEN if_abap_behv=>fc-o-disabled
ELSE if_abap_behv=>fc-o-enabled )
IN
( %tky = Number-%tky
%action-setDefault = is_set_as_default ) ).
ENDMETHOD.
METHOD setDefault.
DATA: lt_no_default TYPE TABLE FOR UPDATE zintlra_i_pb_item.
" Get Parent Keys
READ ENTITIES OF zintlra_i_pb_head IN LOCAL MODE
ENTITY PhoneNumber BY \_hdr
FIELDS ( PbUuid ) WITH CORRESPONDING #( keys )
RESULT DATA(ParentKeys).
READ TABLE keys INTO DATA(key1) INDEX 1.
CLEAR lt_no_default.
LOOP AT ParentKeys INTO DATA(parentkey).
" Get Parent Keys
READ ENTITIES OF zintlra_i_pb_head IN LOCAL MODE
ENTITY Contact BY \_item
FIELDS ( PbItemUuid ) WITH VALUE #( ( %tky = parentkey-%tky ) )
RESULT DATA(Phonekeys).
LOOP AT Phonekeys INTO DATA(pk).
IF pk-%tky NE key1-%tky.
APPEND INITIAL LINE TO lt_no_default ASSIGNING FIELD-SYMBOL(<lfs_nodef>).
<lfs_nodef> = CORRESPONDING #( pk ).
ENDIF.
ENDLOOP.
ENDLOOP.
" Set the Default status
MODIFY ENTITIES OF zintlra_i_pb_head IN LOCAL MODE
ENTITY PhoneNumber
UPDATE
FIELDS ( PbDefault )
WITH VALUE #( FOR key IN keys
( %tky = key-%tky
PbDefault = lc_default-yes ) )
FAILED failed
REPORTED DATA(reported1).
" Set the NOT Default status
IF NOT lt_no_default IS INITIAL.
MODIFY ENTITIES OF zintlra_i_pb_head IN LOCAL MODE
ENTITY PhoneNumber
EXECUTE setNotDefault
FROM CORRESPONDING #( lt_no_default ).
ENDIF.
" Fill the response table
READ ENTITIES OF zintlra_i_pb_head IN LOCAL MODE
ENTITY PhoneNumber
ALL FIELDS WITH CORRESPONDING #( phonekeys )
RESULT DATA(PhoneNumbers).
result = VALUE #( FOR Number IN PhoneNumbers
( %tky = Number-%tky
%param = Number ) ).
ENDMETHOD.
METHOD setPhoneBookItemId.
READ ENTITIES OF zintlra_i_pb_head IN LOCAL MODE
ENTITY PhoneNumber
ALL FIELDS WITH CORRESPONDING #( keys )
RESULT DATA(PhoneNumbers).
DELETE PhoneNumbers WHERE PbItemId IS NOT INITIAL.
CHECK PhoneNumbers IS NOT INITIAL.
READ ENTITIES OF zintlra_i_pb_head IN LOCAL MODE
ENTITY PhoneNumber BY \_hdr
FIELDS ( PbUuid PbId ) WITH CORRESPONDING #( keys )
RESULT DATA(Contacts).
READ TABLE Contacts INTO DATA(Contact) INDEX 1.
" Get Current Max Item#
SELECT max( pb_item_id ) FROM zintlra_pb_d_itm
WHERE pb_uuid = @Contact-PbUuid
INTO @DATA(lv_current_item).
LOOP AT PhoneNumbers ASSIGNING FIELD-SYMBOL(<lfs_number>).
lv_current_item += 10.
<lfs_number>-PbId = Contact-PbId.
<lfs_number>-PbItemId = lv_current_item.
ENDLOOP.
MODIFY ENTITIES OF zintlra_i_pb_head IN LOCAL MODE
ENTITY PhoneNumber
UPDATE
FROM VALUE #( FOR Number IN PhoneNumbers INDEX INTO i (
%tky = Number-%tky
PbId = Number-PbId
PbItemId = Number-PbItemId
%control-PbItemId = if_abap_behv=>mk-on
%control-PbId = if_abap_behv=>mk-on ) )
REPORTED DATA(update_reported).
reported = CORRESPONDING #( DEEP update_reported ).
ENDMETHOD.
METHOD setNotDefault.
" Set the NOT Default status
MODIFY ENTITIES OF zintlra_i_pb_head IN LOCAL MODE
ENTITY PhoneNumber
UPDATE
FIELDS ( PbDefault )
WITH VALUE #( FOR key IN keys
( %tky = key-%tky
PbDefault = lc_default-no ) )
FAILED failed
REPORTED DATA(reported1).
ENDMETHOD.
METHOD validatePhone.
DATA lv_phone TYPE string.
" Read Phone Numbers
READ ENTITIES OF zintlra_i_pb_head IN LOCAL MODE
ENTITY PhoneNumber
ALL FIELDS WITH CORRESPONDING #( keys )
RESULT DATA(PhoneNumbers).
READ ENTITIES OF zintlra_i_pb_head IN LOCAL MODE
ENTITY PhoneNumber BY \_hdr
FROM CORRESPONDING #( PhoneNumbers )
LINK DATA(ContactPhoneNumberLinks).
LOOP AT PhoneNumbers INTO DATA(phonenumber).
" Validate Phone
APPEND VALUE #( %tky = phonenumber-%tky %state_area = 'VALIDATE_PHONE' ) TO reported-phonenumber.
CLEAR lv_phone.
lv_phone = phonenumber-PbTelephone.
REPLACE ALL OCCURRENCES OF ` ` IN lv_phone WITH ''.
IF lv_phone CN '+-1234567890'.
" Invalid
APPEND VALUE #( %tky = phonenumber-%tky ) TO failed-phonenumber.
APPEND VALUE #( %tky = phonenumber-%tky
%state_area = 'VALIDATE_PHONE'
%msg = NEW zcm_intlra_pb(
severity = if_abap_behv_message=>severity-error
textid = zcm_intlra_pb=>invalid_phone
phone = phonenumber-PbTelephone
)
%element-PbTelephone = if_abap_behv=>mk-on
%path = VALUE #( contact-%tky = ContactPhoneNumberLinks[ source-%tky = phonenumber-%tky ]-target-%tky )
)
TO reported-phonenumber.
ENDIF.
ENDLOOP.
ENDMETHOD.
ENDCLASS.
Behavior Projection
Source Code @ ZINTLRA_C_PB_HEAD (Behavior Projection)
projection;
use draft;
define behavior for ZINTLRA_C_PB_HEAD alias Contact
use etag
{
use create;
use update;
use delete;
use action setFavourite;
use action removeFavourite;
use association _item { create; with draft; }
}
define behavior for ZINTLRA_C_PB_ITEM alias PhoneNumber
use etag
{
use update;
use delete;
use action setDefault ;
use association _hdr{ with draft; }
}
BO Runtime
The business object runtime mainly consists of two parts:
The first part is the interaction phase, in which a consumer calls the business object operations to change data and read instances with or without the transactional changes. The business object runtime keeps the changes in its internal transactional buffer which represents the state of the instance data. This transactional buffer is always required for a business object. After all, changes were performed, the data can be persisted. This is realized with the save sequence.
For more information about the BO runtime within one LUW, see The RAP Transactional Model for the SAP LUW.
BO Runtime
For each operation, the transactional runtime is described in detail in the respective runtime diagrams. See Operations.
The save sequence has the same structure for each operation. For more information, see Save Sequence Runtime.
Instantiation of Handler and Saver Classes
In general, the instantiation of handler and saver classes is tightly coupled to an ABAP session. Instances live as long as the ABAP session and they can exist across LUW borders. However, their life cycle depends on the way their methods are called. Nested method invocations always provoke the re-instantiation of the local handler and saver classes. For example, this is the case if a handler method executes an EML to modify the request in local mode to call another method of the same handler class, or if other BOs are involved in the invocation chain.
The blog refers to SAP HELP (Source for RAP Information)