How to Track Data Changes and Change Log Mechanism in RAP.

Estimated read time 13 min read

Hi,

A change log table is a simple yet effective way to track data modifications without storing the complete historical snapshots of records. While the main table continues to hold only the active business data, the change log table captures essential metadata such as the record identifiers, type of change, user, and reason for the update. In a RAP scenario, determinations during the save sequence record these details before the final commit, ensuring a reliable audit trail. This approach not only supports compliance monitoring, reporting, and troubleshooting but also minimizes storage and performance overhead compared to maintaining full shadow tables.

Here we see some major key Advantages. 

Simplified Design – Only one additional table is needed, making the data model and maintenance simpler. Faster Reporting on Changes – The log contains concise, relevant details, making it easier to query and generate audit reports. Regulatory Compliance – Still fulfills many compliance and audit requirements by showing who changed what and when. 

Steps to achieving  the Track Change Log Mechanism in RAP

I have create 2 database tables  

For basic details Capturing the changes record table @EndUserText.label : ‘log chanes’

@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE

@AbapCatalog.tableCategory : #TRANSPARENT

@AbapCatalog.deliveryClass : #A

@AbapCatalog.dataMaintenance : #RESTRICTED

define table zgiri_t_emp_det {
key id : abap.int8 not null;

name : abap.char(30);

department : abap.char(30);

curr_key : abap.cuky;

@Semantics.amount.currencyCode : ‘ zgiri_t_emp_det.curr_key’

salary : abap.curr(15,2);

created_by : syuname;

created_at : timestampl;

changed_by : syuname;

changed_at :timestampl;

}

Table 2. 

@EndUserText.label : ‘log chanes’

@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE

@AbapCatalog.tableCategory : #TRANSPARENT

@AbapCatalog.deliveryClass : #A

@AbapCatalog.dataMaintenance : #RESTRICTED

define table zgiri_t_ch_log1 {

key id : abap.int8 not null;

name : abap.char(30);

old_value : abap.string(0);

new_value : abap.string(0);

changed_by : syuname;

changed_at : timestampl;

}

Step2. 

We have to create root view  on top of the database table. 

@AbapCatalog.viewEnhancementCategory: [#NONE]

@AccessControl.authorizationCheck: #NOT_REQUIRED

@EndUserText.label: ’employee details’

@Metadata.ignorePropagatedAnnotations: true

@ObjectModel.usageType:{

serviceQuality: #X,

sizeCategory: #S,

dataClass: #MIXED

}

define view entity zgiri_i_empl1_details as select from ZGIRI_T_emp_det

association [0..*] to zgiri_i_ch_log1 as _change on _change.id = $projection.id

{

key id as Id,

name as Name,

department as Department,

curr_key as CurrKey,

@semantics.amount.currencyCode: ‘CurrKey’

salary as Salary,

created_by as CreatedBy,

created_at as CreatedAt,

changed_by as ChangedBy,

changed_at as ChangedAt,

_change

}

Then we have to create data definition for change log also. 

@AbapCatalog.viewEnhancementCategory: [#NONE]

@AccessControl.authorizationCheck: #NOT_REQUIRED

@EndUserText.label: ’employee details’

@Metadata.ignorePropagatedAnnotations: true

@ObjectModel.usageType:{

serviceQuality: #X,

sizeCategory: #S,

dataClass: #MIXED

}

define view entity zgiri_i_empl1_details as select from zgiri_i_ch_log1

association [1..1] to zgiri_i_emp_det as _emp on _change.id = $projection.id

{

key id as Id,

field_name as Field_name,

old_value as OldValue,

new_value as NewValue,

created_by as CreatedBy,

_emp

}

 

Step 3. 

for the basic data definition we have create on metadata extension for front end display. 

@Metadata.layer:#CORE

annotate entity zgiri_i_empl_details

with

{

@UI.facet: [

{

id: ‘object’,

label: ‘Basic Data’,

type: #IDENTIFICATION_REFERENCE,

purpose: #STANDARD,

position: 10 }]

@UI.lineItem: [{ label: ‘Id’ , position: 10 }]

@UI.identification: [{ label: ‘Id’ , position: 10 }]

Id;

@UI.lineItem: [{ label: ‘Name’ , position: 20 }]

@UI.identification: [{ label: ‘Name’ , position: 20 }]

Name;

@UI.lineItem: [{ label: ‘Department’ , position: 30 }]

@UI.identification : [{ label: ‘Department’ , position: 30 }]

Department;

@UI.lineItem: [{ label: ‘CurrKey’ , position: 40 }]

@UI.identification : [{ label: ‘CurrKey’ , position: 40 }]

CurrKey;

@UI.lineItem: [{ label: ‘Salary’ , position: 50 }]

@UI.identification : [{ label: ‘Salary’ , position: 50 }]

Salary;

@UI.lineItem: [{ label: ‘CreatedBy’ , position: 60 }]

CreatedBy;

@UI.lineItem: [{ label: ‘CreatedAt’ , position: 70 }]

CreatedAt;

@UI.lineItem: [{ label: ‘ChangedBy’ , position: 80 }]

ChangedBy;

@UI.lineItem: [{ label: ‘ChangedAt’ , position: 90 }]

ChangedAt;

}

Step 4. 

On top of the basic view we have to behavior definition. 

managed implementation in class zbp_giri_i_empl_details unique;

strict ( 2 );

define behavior for zgiri_i_empl_details //alias <alias_name>

persistent table zgiri_t_emp_det

lock master

with additional save

authorization master ( instance )

early numbering

//etag master <field_name>

{

create ( authorization : global );

update;

delete;

field ( readonly ) Id;

mapping for zgiri_t_emp_det

{

ChangedAt = changed_at;

ChangedBy = changed_by;

CreatedAt = created_at;

CreatedBy = created_by;

CurrKey = curr_key;

Department = department;

Id = id;

Name = name;

Salary = salary;

}

}

 

On top the consumption view i have create behavior definition for consumption view. 

 

projection;

strict ( 2 );

define behavior for zgiri_c_empl //alias <alias_name>

{

use create;

use update;

use delete;

}

I’m implementing logic in this class. 

CLASS lsc_zgiri_i_empl_details DEFINITION INHERITING FROM cl_abap_behavior_saver.

PROTECTED SECTION.

METHODS save_modified REDEFINITION.

ENDCLASS.

CLASS lsc_zgiri_i_empl_details IMPLEMENTATION.

METHOD save_modified.

DATA lt_log TYPE STANDARD TABLE OF zgiri_t_ch_logs1.

DATA lt_log1 TYPE STANDARD TABLE OF zgiri_t_ch_logs1.

IF update-zgiri_i_empl_details iS NOT INITIAL.

lt_log = CORRESPONDING #( update-zgiri_i_empl_details ).

LOOP AT update-zgiri_i_empl_details ASSIGNING FIELD-SYMBOL(<ls_log_update>).

ASSIGN lt_log[ id = <ls_log_update>-Id ] TO FIELD-SYMBOL(<ls_log_u>) .

* / NAME UPDATE…………………………………..

if <ls_log_update>-%control-Name = if_abap_behv=>mk-on.

<ls_log_u>-name = <ls_log_update>-Name.

TRY.

<ls_log_u>-id = cl_system_uuid=>create_uuid_x16_static( ).

CATCH cx_uuid_error.

“handle exception

ENDTRY.

APPEND <ls_log_u> TO lt_log1.

ENDIF.

ENDLOOP.
INSERT zgiri_t_ch_logs1 FROM TABLE _log1.

ENDIF.
ENDMETHOD.
ENDCLASS.

CLASS lhc_zgiri_i_empl_details DEFINITION INHERITING FROM cl_abap_behavior_handler.

PUBLIC SECTION.

PRIVATE SECTION.
METHODS get_instance_authorizations FOR INSTANCE AUTHORIZATION

IMPORTING keys REQUEST requested_authorizations FOR zgiri_i_empl_details RESULT result.

METHODS get_global_authorizations FOR GLOBAL AUTHORIZATION

IMPORTING REQUEST requested_authorizations FOR zgiri_i_empl_details RESULT result.

METHODS earlynumbering_create FOR NUMBERING

IMPORTING entities FOR CREATE zgiri_i_empl_details.

ENDCLASS.

CLASS lhc_zgiri_i_empl_details IMPLEMENTATION.

METHOD get_instance_authorizations.

ENDMETHOD.

METHOD get_global_authorizations.

ENDMETHOD.

METHOD earlynumbering_create.

DATA(lt_entities) = entities.

DELETE lt_entities WHERE Id IS NOT INITIAL.

TRY.

cl_numberrange_runtime=>number_get(

EXPORTING

nr_range_nr = ’01’

object = ‘/DMO/TRV_M’

quantity = CONV #( lines( lt_entities ) )

IMPORTING

number = DATA(lv_latest_num)

returncode = DATA(lv_code)

returned_quantity = DATA(lv_qty)

).

CATCH cx_nr_object_not_found.

CATCH cx_number_ranges INTO DATA(lo_error).

LOOP AT lt_entities INTO DATA(ls_entities).

APPEND VALUE #( %cid = ls_entities-%cid

%key = ls_entities-%key )

TO failed-zgiri_i_empl_details.

APPEND VALUE #( %cid = ls_entities-%cid

%key = ls_entities-%key

%msg = lo_error )

TO reported-zgiri_i_empl_details.

ENDLOOP.

EXIT.

ENDTRY.

ASSERT lv_qty = lines( lt_entities ).

* DATA: lt_travel_tech_m TYPE TABLE FOR MAPPED EARLY yi_travel_tech_m,

* ls_travel_tech_m LIKE LINE OF lt_travel_tech_m.

DATA(lv_curr_num) = lv_latest_num – lv_qty.

LOOP AT lt_entities INTO ls_entities.

lv_curr_num = lv_curr_num + 1.

APPEND VALUE #( %cid = ls_entities-%cid

ID = lv_curr_num )

TO mapped-zgiri_i_empl_details.

ENDLOOP.

ENDMETHOD.

ENDCLASS.

 

On top the behavior definition we have to create service definition. 

@EndUserText.label: ‘service definition’

define service Zgiri_rap_scn_ser {

expose zgiri_i_empl_details;

}

On top the service definition we have to create  service binding.

 

after that we can preview our application.

Im taking name as example im going to update the name sham signiwis this value.  

see we can update shyam prasad.

And we see in the chang log table. 

thank you 
if u have any query reach out me

 

 

 

 

 

 

​ Hi,A change log table is a simple yet effective way to track data modifications without storing the complete historical snapshots of records. While the main table continues to hold only the active business data, the change log table captures essential metadata such as the record identifiers, type of change, user, and reason for the update. In a RAP scenario, determinations during the save sequence record these details before the final commit, ensuring a reliable audit trail. This approach not only supports compliance monitoring, reporting, and troubleshooting but also minimizes storage and performance overhead compared to maintaining full shadow tables.Here we see some major key Advantages. Simplified Design – Only one additional table is needed, making the data model and maintenance simpler. Faster Reporting on Changes – The log contains concise, relevant details, making it easier to query and generate audit reports. Regulatory Compliance – Still fulfills many compliance and audit requirements by showing who changed what and when. Steps to achieving  the Track Change Log Mechanism in RAPI have create 2 database tables  For basic details Capturing the changes record table @EndUserText.label : ‘log chanes’

@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE

@AbapCatalog.tableCategory : #TRANSPARENT

@AbapCatalog.deliveryClass : #A

@AbapCatalog.dataMaintenance : #RESTRICTED

define table zgiri_t_emp_det {
key id : abap.int8 not null;

name : abap.char(30);

department : abap.char(30);

curr_key : abap.cuky;

@Semantics.amount.currencyCode : ‘ zgiri_t_emp_det.curr_key’

salary : abap.curr(15,2);

created_by : syuname;

created_at : timestampl;

changed_by : syuname;

changed_at :timestampl;

}

Table 2. @EndUserText.label : ‘log chanes’

@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE

@AbapCatalog.tableCategory : #TRANSPARENT

@AbapCatalog.deliveryClass : #A

@AbapCatalog.dataMaintenance : #RESTRICTED

define table zgiri_t_ch_log1 {

key id : abap.int8 not null;

name : abap.char(30);

old_value : abap.string(0);

new_value : abap.string(0);

changed_by : syuname;

changed_at : timestampl;

}

Step2. We have to create root view  on top of the database table. @AbapCatalog.viewEnhancementCategory: [#NONE]

@AccessControl.authorizationCheck: #NOT_REQUIRED

@EndUserText.label: ’employee details’

@Metadata.ignorePropagatedAnnotations: true

@ObjectModel.usageType:{

serviceQuality: #X,

sizeCategory: #S,

dataClass: #MIXED

}

define view entity zgiri_i_empl1_details as select from ZGIRI_T_emp_det

association [0..*] to zgiri_i_ch_log1 as _change on _change.id = $projection.id

{

key id as Id,

name as Name,

department as Department,

curr_key as CurrKey,

@semantics.amount.currencyCode: ‘CurrKey’

salary as Salary,

created_by as CreatedBy,

created_at as CreatedAt,

changed_by as ChangedBy,

changed_at as ChangedAt,

_change

}

Then we have to create data definition for change log also. @AbapCatalog.viewEnhancementCategory: [#NONE]

@AccessControl.authorizationCheck: #NOT_REQUIRED

@EndUserText.label: ’employee details’

@Metadata.ignorePropagatedAnnotations: true

@ObjectModel.usageType:{

serviceQuality: #X,

sizeCategory: #S,

dataClass: #MIXED

}

define view entity zgiri_i_empl1_details as select from zgiri_i_ch_log1

association [1..1] to zgiri_i_emp_det as _emp on _change.id = $projection.id

{

key id as Id,

field_name as Field_name,

old_value as OldValue,

new_value as NewValue,

created_by as CreatedBy,

_emp

}

 Step 3. for the basic data definition we have create on metadata extension for front end display. @Metadata.layer:#CORE

annotate entity zgiri_i_empl_details

with

{

@UI.facet: [

{

id: ‘object’,

label: ‘Basic Data’,

type: #IDENTIFICATION_REFERENCE,

purpose: #STANDARD,

position: 10 }]

@UI.lineItem: [{ label: ‘Id’ , position: 10 }]

@UI.identification: [{ label: ‘Id’ , position: 10 }]

Id;

@UI.lineItem: [{ label: ‘Name’ , position: 20 }]

@UI.identification: [{ label: ‘Name’ , position: 20 }]

Name;

@UI.lineItem: [{ label: ‘Department’ , position: 30 }]

@UI.identification : [{ label: ‘Department’ , position: 30 }]

Department;

@UI.lineItem: [{ label: ‘CurrKey’ , position: 40 }]

@UI.identification : [{ label: ‘CurrKey’ , position: 40 }]

CurrKey;

@UI.lineItem: [{ label: ‘Salary’ , position: 50 }]

@UI.identification : [{ label: ‘Salary’ , position: 50 }]

Salary;

@UI.lineItem: [{ label: ‘CreatedBy’ , position: 60 }]

CreatedBy;

@UI.lineItem: [{ label: ‘CreatedAt’ , position: 70 }]

CreatedAt;

@UI.lineItem: [{ label: ‘ChangedBy’ , position: 80 }]

ChangedBy;

@UI.lineItem: [{ label: ‘ChangedAt’ , position: 90 }]

ChangedAt;

}

Step 4. On top of the basic view we have to behavior definition. managed implementation in class zbp_giri_i_empl_details unique;

strict ( 2 );

define behavior for zgiri_i_empl_details //alias <alias_name>

persistent table zgiri_t_emp_det

lock master

with additional save

authorization master ( instance )

early numbering

//etag master <field_name>

{

create ( authorization : global );

update;

delete;

field ( readonly ) Id;

mapping for zgiri_t_emp_det

{

ChangedAt = changed_at;

ChangedBy = changed_by;

CreatedAt = created_at;

CreatedBy = created_by;

CurrKey = curr_key;

Department = department;

Id = id;

Name = name;

Salary = salary;

}

}

 On top the consumption view i have create behavior definition for consumption view.  projection;

strict ( 2 );

define behavior for zgiri_c_empl //alias <alias_name>

{

use create;

use update;

use delete;

}

I’m implementing logic in this class. CLASS lsc_zgiri_i_empl_details DEFINITION INHERITING FROM cl_abap_behavior_saver.

PROTECTED SECTION.

METHODS save_modified REDEFINITION.

ENDCLASS.

CLASS lsc_zgiri_i_empl_details IMPLEMENTATION.

METHOD save_modified.

DATA lt_log TYPE STANDARD TABLE OF zgiri_t_ch_logs1.

DATA lt_log1 TYPE STANDARD TABLE OF zgiri_t_ch_logs1.

IF update-zgiri_i_empl_details iS NOT INITIAL.

lt_log = CORRESPONDING #( update-zgiri_i_empl_details ).

LOOP AT update-zgiri_i_empl_details ASSIGNING FIELD-SYMBOL(<ls_log_update>).

ASSIGN lt_log[ id = <ls_log_update>-Id ] TO FIELD-SYMBOL(<ls_log_u>) .

* / NAME UPDATE…………………………………..

if <ls_log_update>-%control-Name = if_abap_behv=>mk-on.

<ls_log_u>-name = <ls_log_update>-Name.

TRY.

<ls_log_u>-id = cl_system_uuid=>create_uuid_x16_static( ).

CATCH cx_uuid_error.

“handle exception

ENDTRY.

APPEND <ls_log_u> TO lt_log1.

ENDIF.

ENDLOOP.
INSERT zgiri_t_ch_logs1 FROM TABLE _log1.

ENDIF.
ENDMETHOD.
ENDCLASS.

CLASS lhc_zgiri_i_empl_details DEFINITION INHERITING FROM cl_abap_behavior_handler.

PUBLIC SECTION.

PRIVATE SECTION.
METHODS get_instance_authorizations FOR INSTANCE AUTHORIZATION

IMPORTING keys REQUEST requested_authorizations FOR zgiri_i_empl_details RESULT result.

METHODS get_global_authorizations FOR GLOBAL AUTHORIZATION

IMPORTING REQUEST requested_authorizations FOR zgiri_i_empl_details RESULT result.

METHODS earlynumbering_create FOR NUMBERING

IMPORTING entities FOR CREATE zgiri_i_empl_details.

ENDCLASS.

CLASS lhc_zgiri_i_empl_details IMPLEMENTATION.

METHOD get_instance_authorizations.

ENDMETHOD.

METHOD get_global_authorizations.

ENDMETHOD.

METHOD earlynumbering_create.

DATA(lt_entities) = entities.

DELETE lt_entities WHERE Id IS NOT INITIAL.

TRY.

cl_numberrange_runtime=>number_get(

EXPORTING

nr_range_nr = ’01’

object = ‘/DMO/TRV_M’

quantity = CONV #( lines( lt_entities ) )

IMPORTING

number = DATA(lv_latest_num)

returncode = DATA(lv_code)

returned_quantity = DATA(lv_qty)

).

CATCH cx_nr_object_not_found.

CATCH cx_number_ranges INTO DATA(lo_error).

LOOP AT lt_entities INTO DATA(ls_entities).

APPEND VALUE #( %cid = ls_entities-%cid

%key = ls_entities-%key )

TO failed-zgiri_i_empl_details.

APPEND VALUE #( %cid = ls_entities-%cid

%key = ls_entities-%key

%msg = lo_error )

TO reported-zgiri_i_empl_details.

ENDLOOP.

EXIT.

ENDTRY.

ASSERT lv_qty = lines( lt_entities ).

* DATA: lt_travel_tech_m TYPE TABLE FOR MAPPED EARLY yi_travel_tech_m,

* ls_travel_tech_m LIKE LINE OF lt_travel_tech_m.

DATA(lv_curr_num) = lv_latest_num – lv_qty.

LOOP AT lt_entities INTO ls_entities.

lv_curr_num = lv_curr_num + 1.

APPEND VALUE #( %cid = ls_entities-%cid

ID = lv_curr_num )

TO mapped-zgiri_i_empl_details.

ENDLOOP.

ENDMETHOD.

ENDCLASS.
 On top the behavior definition we have to create service definition. @EndUserText.label: ‘service definition’

define service Zgiri_rap_scn_ser {

expose zgiri_i_empl_details;

}

On top the service definition we have to create  service binding. after that we can preview our application.Im taking name as example im going to update the name sham signiwis this value.  see we can update shyam prasad.And we see in the chang log table. thank you if u have any query reach out me        Read More Application Development and Automation Blog Posts articles 

#SAP

You May Also Like

More From Author