
ABAP Cloud - Reusable Components
What points should you consider when developing reusable components in ABAP Cloud? Here we look at various examples.
Table of contents
In this article, we look at working across software components and the challenges associated with using these components.
Introduction
Every system typically has a handful of components that are reused during development. Such central components are usually provided and maintained centrally. Such components include, for example, a facade for the application log, templates and services for sending emails, parameter tables, or various configurations. In the ABAP Cloud area, we use various software components to separate the components of our applications.
We have summarized why we use many TIER-1 software components (SWC) in this article. This involves the deliberate separation of code and DDIC elements to bring more structure and clean encapsulation to the system. Finally, we consider which objects we share with others.
Scenario
In this case, we use two classes and an interface. One class is a reuse component, and the other class implements some methods and has an interface where types are defined that we test in our scenario. The artifacts' structure is as follows.
Release
Since we are working with a reusable component and this is located in another software component, we must define a C1 Contract and thus enter into a stability contract. The object thus becomes our own released API in the system. If we want to use the class without sharing, we get the following error message.
In the next step, we should define our C1 Contract on the class.
Project Explorer
If you select the object in the Project Explorer and right-click to open the context menu, you will find an action in the lower area for suitable objects.
Properties
Once the object is open, you can switch to the "Properties" view and find the "API State" under the corresponding contract information. You can then access the standard release flow using the "+" button.
Flow
You will then go through the standard release flow for the objects. It is important that the C1 contract is used for the use of the object and that the "Use in Cloud Development" setting is set so that we can use the object within ABAP Cloud.
Finally, the entry is transferred to the transport, where some table entries are transported for release. The whole thing is summarized under the object type "APIS".
Data type
What about the general use of data types in cross-components? In this case, we assume that the type is located outside the Re-Use component in the calling SWC.
Preparation
To do this, we create a data type in the ZIF_BS_DEMO_SWC_USE interface, which we want to have populated in our cross-component. The type consists of different fields with different data types.
TYPES charlike TYPE c LENGTH 25.
TYPES packed TYPE p LENGTH 15 DECIMALS 2.
TYPES: BEGIN OF dummy,
number TYPE i,
char TYPE charlike,
string TYPE string,
packed TYPE packed,
timestamp TYPE utclong,
END OF dummy.
TYPES dummys TYPE STANDARD TABLE OF dummy WITH EMPTY KEY.
Reference
In this scenario, we pass the data type into the method as a reference. To do this, we define our reuse method as follows:
METHODS table_reference
IMPORTING !data TYPE REF TO data.
In the method implementation, we first need a field symbol to assign the reference. In the next step, we would then assign the data to the field symbol to return it.
METHOD table_reference.
FIELD-SYMBOLS <table> TYPE STANDARD TABLE.
ASSIGN data->* TO <table>.
DATA(parts) = get_default_parts( ).
<table> = CORRESPONDING #( parts ).
ENDMETHOD.
However, this method does not work because a SY-SUBRC of 4 is already set during ASSIGN. We currently have no access to the type from the interface.
Changing
In the next attempt, we use a Changing parameter to pass the table into the method from outside and fill it there.
METHODS table_changing
CHANGING !data TYPE ANY TABLE.
The method implementation is quite simple; we simply create our dummy data and assign it to the Changing parameter using CORRESPONDING.
METHOD table_changing.
DATA(parts) = get_default_parts( ).
data = CORRESPONDING #( parts ).
ENDMETHOD.
This variant works so far, and the correspondingly filled fields reach the caller. In the example, you'll also find the same example for the topic EXPORTING; this scenario also works.
Dynamic
In the last example, we pass the type dynamically as a string and expect the routine to return the data as a generated reference from the type.
METHODS table_with_type
IMPORTING type_name TYPE string
RETURNING VALUE(data) TYPE REF TO data.
During implementation, we dynamically create our data reference using the supplied type, assign the reference to the new field symbol, and pass the data.
METHOD table_with_type.
CREATE DATA data TYPE (type_name).
ASSIGN data->* TO FIELD-SYMBOL(<data>).
DATA(parts) = get_default_parts( ).
<data> = CORRESPONDING #( parts ).
ENDMETHOD.
In this case, we receive a dump immediately because the CREATE doesn't work. However, the message indicates that the type is unknown. With dynamic assignment, the type cannot be checked first, and therefore the message is not as precise.
Solution
In principle, you can also equip the ZIF_BS_DEMO_SWC_USE interface with a C1 release, but the interface would then be released throughout the entire system and could also be used by others, which could lead to dependencies. The cleanest approach would therefore be to create a "Software Component Relation." There, we release our components for the SWC of the cross-components (access permission).
If we now execute all methods of our class again, all procedures will work. Currently, however, the Software Component Relations are only available in the ABAP Environment and the S/4HANA Cloud Public Edition and will most likely be available in 2025.
Application Log
In the next example, we'll look at creating an application log. In most cases, we use a facade for the application log, since even with the new interfaces, transferring messages still requires a relatively large amount of code, as does initialization.
Scenario
To do this, we define a method in our cross-component that receives a table of messages and then stores them in an application log.
METHODS save_messages
IMPORTING !messages TYPE messages
!header TYPE REF TO if_bali_header_setter OPTIONAL.
The implementation of the method is quite long, so here's a brief explanation. We create an Application Log object and then a corresponding header if none was passed. We then transfer the header to our Log object, save the message, and finally call SAVE to persist the messages to the log.
METHOD save_messages.
DATA(application_log) = cl_bali_log=>create( ).
DATA(local_header) = header.
IF local_header IS INITIAL.
local_header = cl_bali_header_setter=>create( object = 'ZBS_APPL_REUSE'
subobject = 'TEST'
external_id = CONV #( xco_cp=>uuid( )->value ) ).
ENDIF.
local_header->set_expiry( expiry_date = CONV d( cl_abap_context_info=>get_system_date( ) + 7 )
keep_until_expiry = abap_true ).
application_log->set_header( local_header ).
LOOP AT messages INTO DATA(message).
application_log->add_item( cl_bali_message_setter=>create_from_bapiret2( message ) ).
ENDLOOP.
cl_bali_log_db=>get_instance( )->save_log( application_log ).
ENDMETHOD.
The Application Log object "ZBS_APPL_REUSE" used in this case is located in our calling component, i.e., the application that uses the cross-component. For simplicity, we have hard-coded the object.
Error
If we now execute the method, an error occurs when creating the header in the method, and our routine aborts. When debugging, you will notice that the standard checks whether our Application Log object has been released. This means our component cannot create the header and therefore cannot save the log.
Solution
In this example, there are two solutions that can help us achieve our goal:
- Creating the header (CL_BALI_HEADER_SETTER) in the calling component and passing it to the Save method.
- Using Software Component Relations to release all objects for the cross-component components.
Adaptation
If you want to adapt shared APIs, this is generally possible, but you will also be prompted with a security message. when editing.
Therefore, you should follow the standard rules for changing artifacts.
Complete example
Here you can find all the resources shown in the article again; we have omitted GitHub this time.
Interface
The interface provides a type.
INTERFACE zif_bs_demo_swc_use
PUBLIC.
TYPES charlike TYPE c LENGTH 25.
TYPES packed TYPE p LENGTH 15 DECIMALS 2.
TYPES: BEGIN OF dummy,
number TYPE i,
char TYPE charlike,
string TYPE string,
packed TYPE packed,
timestamp TYPE utclong,
END OF dummy.
TYPES dummys TYPE STANDARD TABLE OF dummy WITH EMPTY KEY.
ENDINTERFACE.
Classes
The class should consume the cross-component.
CLASS zcl_bs_demo_swc_use DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
INTERFACES zif_bs_demo_swc_use.
PRIVATE SECTION.
METHODS call_method
IMPORTING !out TYPE REF TO if_oo_adt_classrun_out.
METHODS call_with_changing
IMPORTING !out TYPE REF TO if_oo_adt_classrun_out.
METHODS call_with_reference
IMPORTING !out TYPE REF TO if_oo_adt_classrun_out.
METHODS call_for_reference
IMPORTING !out TYPE REF TO if_oo_adt_classrun_out.
METHODS call_with_exporting
IMPORTING !out TYPE REF TO if_oo_adt_classrun_out.
METHODS call_application_log
IMPORTING !out TYPE REF TO if_oo_adt_classrun_out.
ENDCLASS.
CLASS zcl_bs_demo_swc_use IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
call_method( out ).
call_with_changing( out ).
call_with_reference( out ).
call_for_reference( out ).
call_with_exporting( out ).
call_application_log( out ).
ENDMETHOD.
METHOD call_method.
DATA(dummy_data) = VALUE zif_bs_demo_swc_use=>dummys(
( number = 1 char = 'one' string = `First field` packed = '1.11' timestamp = utclong_current( ) )
( number = 2 char = 'two' string = `Second field` packed = '2.22' timestamp = utclong_current( ) ) ).
out->write( dummy_data ).
ENDMETHOD.
METHOD call_with_changing.
DATA changed_data TYPE zif_bs_demo_swc_use=>dummys.
DATA(reuse) = NEW zcl_bs_demo_swc_reuse( ).
reuse->table_changing( CHANGING data = changed_data ).
out->write( changed_data ).
ENDMETHOD.
METHOD call_with_reference.
DATA changed_data TYPE zif_bs_demo_swc_use=>dummys.
DATA(reuse) = NEW zcl_bs_demo_swc_reuse( ).
reuse->table_reference( data = REF #( changed_data ) ).
out->write( changed_data ).
ENDMETHOD.
METHOD call_for_reference.
DATA(reuse) = NEW zcl_bs_demo_swc_reuse( ).
DATA(reference) = reuse->table_with_type( type_name = 'ZIF_BS_DEMO_SWC_USE=>DUMMYS' ).
out->write( reference->* ).
ENDMETHOD.
METHOD call_with_exporting.
DATA changed_data TYPE zif_bs_demo_swc_use=>dummys.
DATA(reuse) = NEW zcl_bs_demo_swc_reuse( ).
reuse->table_exporting( IMPORTING data = changed_data ).
out->write( changed_data ).
ENDMETHOD.
METHOD call_application_log.
DATA(messages) = VALUE zcl_bs_demo_swc_reuse=>messages( id = 'ZDUMMY'
( type = 'S' number = '001' )
( type = 'W' number = '002' ) ).
* DATA(header) = cl_bali_header_setter=>create( object = 'ZBS_APPL_REUSE'
* subobject = 'TEST'
* external_id = CONV #( xco_cp=>uuid( )->value ) ).
DATA(reuse) = NEW zcl_bs_demo_swc_reuse( ).
reuse->save_messages( messages = messages
* header = header
).
out->write( messages ).
ENDMETHOD.
ENDCLASS.
The class represents our overarching component.
CLASS zcl_bs_demo_swc_reuse DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
TYPES messages TYPE STANDARD TABLE OF bapiret2 WITH EMPTY KEY.
METHODS table_changing
CHANGING !data TYPE ANY TABLE.
METHODS table_reference
IMPORTING !data TYPE REF TO data.
METHODS table_with_type
IMPORTING type_name TYPE string
RETURNING VALUE(data) TYPE REF TO data.
METHODS table_exporting
EXPORTING !data TYPE ANY TABLE.
METHODS save_messages
IMPORTING !messages TYPE messages
!header TYPE REF TO if_bali_header_setter OPTIONAL.
PRIVATE SECTION.
TYPES: BEGIN OF part,
number TYPE i,
string TYPE string,
timestamp TYPE utclong,
END OF part.
TYPES parts TYPE STANDARD TABLE OF part WITH EMPTY KEY.
METHODS get_default_parts
RETURNING VALUE(result) TYPE parts.
ENDCLASS.
CLASS zcl_bs_demo_swc_reuse IMPLEMENTATION.
METHOD table_changing.
DATA(parts) = get_default_parts( ).
data = CORRESPONDING #( parts ).
ENDMETHOD.
METHOD table_reference.
FIELD-SYMBOLS <table> TYPE STANDARD TABLE.
ASSIGN data->* TO <table>.
DATA(parts) = get_default_parts( ).
<table> = CORRESPONDING #( parts ).
ENDMETHOD.
METHOD table_with_type.
CREATE DATA data TYPE (type_name).
ASSIGN data->* TO FIELD-SYMBOL(<data>).
DATA(parts) = get_default_parts( ).
<data> = CORRESPONDING #( parts ).
ENDMETHOD.
METHOD table_exporting.
DATA(parts) = get_default_parts( ).
data = CORRESPONDING #( parts ).
ENDMETHOD.
METHOD get_default_parts.
RETURN VALUE parts( ( number = 13 string = `Thirteen` timestamp = utclong_current( ) )
( number = 20 string = `Twenty` ) ).
ENDMETHOD.
METHOD save_messages.
DATA(application_log) = cl_bali_log=>create( ).
DATA(local_header) = header.
IF local_header IS INITIAL.
local_header = cl_bali_header_setter=>create( object = 'ZBS_APPL_REUSE'
subobject = 'TEST'
external_id = CONV #( xco_cp=>uuid( )->value ) ).
ENDIF.
local_header->set_expiry( expiry_date = CONV d( cl_abap_context_info=>get_system_date( ) + 7 )
keep_until_expiry = abap_true ).
application_log->set_header( local_header ).
LOOP AT messages INTO DATA(message).
application_log->add_item( cl_bali_message_setter=>create_from_bapiret2( message ) ).
ENDLOOP.
cl_bali_log_db=>get_instance( )->save_log( application_log ).
ENDMETHOD.
ENDCLASS.
Conclusion
The development of cross-components still works in the new world, but it is associated with certain risks and obstacles when it comes to a clean implementation in all cases. However, using the examples, you should be able to eliminate many problems.