How To Integrate SAP S/4HANA BOR Framework with SAP Integration Suite, advanced event mesh (AEM) using SAP Application Interface Framework (AIF)
Events are becoming increasingly popular for integration. Consequently, I’ve started to connect an SAP S/4HANA system and an SAP ERP system with SAP Integration Suite, advanced event mesh (AEM) using SAP Application Interface Framework (AIF).
I chose AIF because of its myriad of capabilities such as an integrated runtime and monitoring. Other significant advantages are its availability as an add-on in SAP ERP and as an inherent part of the SAP S/4HANA foundation.
As example, I chose the change event of the business partner within an SAP ERP 731 system.
The following result is sent to AEM:
{
"specversion": "1.0",
"type": "aif.businesspartner.change",
"source": "ZUX/200",
"datacontenttype": "application/json",
"id": "sOcLMMBC7k{BmRa}xTN6e0",
"time": "2024-06-19T07:30:24Z",
"data": {
"BusinessPartner": "A10",
"LastName": "Sisko",
"FirstName": "Benjamin",
"Country": "US"
}
}
Process shown as diagram:
Step 1: Enhancing the BOR framework
First, I linked an AIF-specific function module to the business partner change event in transaction Maintain Event Type Linkages (SWE2). This function module hands over the event data to the AIF XML runtime in a specific AIF/BOR event structure that reflects the import parameter of the receiver function module.
FUNCTION zaif_event_integration .
*"--------------------------------------------------------------------
*"*"Local Interface:
*" IMPORTING
*" VALUE(OBJTYPE) LIKE SWETYPECOU-OBJTYPE
*" VALUE(OBJKEY) LIKE SWEINSTCOU-OBJKEY
*" VALUE(EVENT) LIKE SWEINSTCOU-EVENT
*" VALUE(RECTYPE) LIKE SWETYPECOU-RECTYPE
*" TABLES
*" EVENT_CONTAINER STRUCTURE SWCONT
*"--------------------------------------------------------------------
DATA: ls_event_raw TYPE zaif_event_raw.
ls_event_raw-objtype = objtype.
ls_event_raw-objkey = objkey.
ls_event_raw-event = event.
ls_event_raw-rectype = rectype.
TRY.
CALL METHOD /aif/cl_enabler_xml=>transfer_to_aif
EXPORTING
is_any_structure = ls_event_raw
CATCH cx_root INTO DATA(lx_root).
MESSAGE lx_root->get_text( ) TYPE 'E'.
ENDTRY.
ENDFUNCTION.
Step 2: RFC Destination Type G
I created a RFC destination type G for the communication with the Advanced Event Mesh in transaction RFC Destinations (SM59). As authentication I chose “Basic Authentication”.
Step 3: Developing the AIF interface
Interface customizing
Next, I defined an AIF interface in transaction AIF Customizing (/AIF/CUST) –> Define Interfaces. Overall, this interface collects additional business partner information (first name, last name and country) from the business partner and transmits it to AEM in a CloudEvent format. The raw structure is the previously defined AIF/BOR Event structure.
The target structure, or SAP structure, contains all needed information for the AEM.
The CONTROLLER sub-structure holds all information for the processing within the AIF and the communication with AEM:
The DATA substructures holds the CloudEvent represented as DDIC structure:
Engine Settings
As the interface is handled as an XML interface, I set the engines accordingly in AIF Customizing (/AIF/CUST) –> Additional Interface Porperties –> Specify Interface Engines to XML runtime:
Structure Mapping – Payload Component
The header fields DATACONTENTTYPE, SPECVERSION and TYPE are filled via fix values. Structure Mapping is done in AIF Customizing (/AIF/CUST) –> Define Structure Mapping
The header fields ID, TIME and SOURCE are filled via value mapping function modules in the field mapping:
The coding I used for the ID creation:
cl_uuid_factory=>create_system_uuid( )->create_uuid_c22( ).
For the time determination:
DATA: time_stamp TYPE timestamp,
dat TYPE d,
tim TYPE t,
tz TYPE ttzz-tzone,
dst TYPE c LENGTH 1.
GET TIME STAMP FIELD time_stamp.
tz = 'UTC'.
CONVERT TIME STAMP time_stamp TIME ZONE tz INTO DATE dat TIME tim DAYLIGHT SAVING TIME dst .
value_out = |{ dat DATE = ISO }{ 'T' } { tim TIME = ISO }{ 'Z' } | .
CONDENSE value_out NO-GAPS.
For the SOURCE:
sy-sysid && '/' && sy-mandt
The fields of the data structure are filled using value mappings:
The value mappings for FIRST_NAME and LAST_NAME read data directly from the business partner table BUT000 using a db select:
The COUNTRY field is read by function module using BP function modules:
DATA: lv_partner TYPE bu_partner.
DATA: lt_address TYPE STANDARD TABLE OF bus020_ext.
lv_partner = value_in.
CALL FUNCTION 'BUA_ADDRESS_GET_ALL'
EXPORTING
i_partner = lv_partner
TABLES
t_address = lt_address
EXCEPTIONS
no_address_found = 1
wrong_parameters = 2
internal_error = 3
date_invalid = 4
not_valid = 5
partner_blocked = 6
OTHERS = 7.
IF sy-subrc <> 0.
clear value_out.
ELSE.
TRY.
value_out = lt_address[ 1 ]-country.
CATCH cx_sy_itab_line_not_found.
clear value_out.
ENDTRY.
ENDIF.
Structure Mapping – Controller Component
Within the controller component, the fields NAME_MAPPING_ID, RFC_DEST and URI are filled with Fix Values.
The field TOPIC is determined based on OBJTYPE and COUNTRY of the payload.
Defined Multi Values:
OBJTYPE COUNTRY TOPIC
BUS1006 | DE | aif.businesspartner.change.de |
BUS1006 | EN | aif.businesspartner.change.en |
Action
Next, I assigned an AEM integration action to the structure mapping:
Following the coding of the function module, it takes the populated SAP structure, employs the CONTROLLER information to establish the HTTP connection and URI, converts the payload into JSON and transmits it to AEM. Ultimately, the result is returned for status evaluation and logging within the AIF.
The coding as it works for NW 7.31 up. It’s coded generically, so it can be used for every event and payload, when the SAP structure and mapping follows the same schema.
To get the function module activated, also the name mapping table must be created
FUNCTION zaif_action_call_aem .
*"----------------------------------------------------------------------
*"*"Local Interface:
*" IMPORTING
*" REFERENCE(TESTRUN) TYPE C
*" REFERENCE(SENDING_SYSTEM) TYPE /AIF/AIF_BUSINESS_SYSTEM_KEY
*" OPTIONAL
*" TABLES
*" RETURN_TAB STRUCTURE BAPIRET2
*" CHANGING
*" REFERENCE(DATA)
*" REFERENCE(CURR_LINE)
*" REFERENCE(SUCCESS) TYPE /AIF/SUCCESSFLAG
*" REFERENCE(OLD_MESSAGES) TYPE /AIF/BAL_T_MSG
*"----------------------------------------------------------------------
DATA: lv_json TYPE string.
DATA: lv_status_code TYPE i.
DATA: lv_status_reason TYPE string.
DATA: lr_http_client TYPE REF TO if_http_client.
DATA: ls_bapiret2 TYPE bapiret2.
DATA: ls_name_mapping TYPE /ui2/cl_json=>name_mapping.
DATA: lt_name_mappings TYPE /ui2/cl_json=>name_mappings.
FIELD-SYMBOLS: <ls_data> TYPE any.
FIELD-SYMBOLS: <ls_controller> TYPE zaif_aem_controller.
DATA: lv_uri TYPE string.
DATA: ls_aif_aem_name_ma TYPE zaif_aem_name_ma.
DATA: lt_aif_aem_name_ma TYPE TABLE OF zaif_aem_name_ma.
ASSIGN COMPONENT 'DATA' OF STRUCTURE data TO <ls_data>.
ASSIGN COMPONENT 'CONTROLLER' OF STRUCTURE data TO <ls_controller>.
SELECT * FROM zaif_aem_name_ma INTO TABLE lt_aif_aem_name_ma WHERE name_mapping_id = <ls_controller>-name_mapping_id.
LOOP AT lt_aif_aem_name_ma INTO ls_aif_aem_name_ma.
MOVE-CORRESPONDING ls_aif_aem_name_ma TO ls_name_mapping.
INSERT ls_name_mapping INTO TABLE lt_name_mappings.
ENDLOOP.
/aif/cl_json=>serialize( " for NW 7.55 ff, /ui2_cl_json must be used
EXPORTING
data = <ls_data>
name_mappings = lt_name_mappings
RECEIVING
r_json = lv_json ).
cl_http_client=>create_by_destination(
EXPORTING
destination = <ls_controller>-rfc_dest
IMPORTING
client = lr_http_client " HTTP Client Abstraction
EXCEPTIONS
argument_not_found = 1 " Connection Parameter (Destination) Not Available
destination_not_found = 2 " Destination not found
destination_no_authority = 3 " No Authorization to Use HTTP Destination
plugin_not_active = 4 " HTTP/HTTPS communication not available
internal_error = 5 " Internal error (e.g. name too long)
OTHERS = 6
).
IF sy-subrc <> 0.
ls_bapiret2-id = sy-msgid.ls_bapiret2-number = sy-msgno.ls_bapiret2-type = sy-msgty.
ls_bapiret2-message_v1 = sy-msgv1.ls_bapiret2-message_v2 = sy-msgv2.ls_bapiret2-message_v3 = sy-msgv3.ls_bapiret2-message_v4 = sy-msgv4.
APPEND ls_bapiret2 TO return_tab.RETURN.
ENDIF.
lr_http_client->request->set_header_field( name = 'Content-Type' ##NO_TEXT
value = 'application/json' ).
lr_http_client->request->set_method( 'POST' ).
lr_http_client->request->set_cdata(
data = lv_json ).
lv_uri = <ls_controller>-uri.
cl_http_utility=>set_request_uri(
EXPORTING
request = lr_http_client->request
uri = lv_uri
).
lr_http_client->send(
EXCEPTIONS
http_communication_failure = 1
http_invalid_state = 2
http_processing_failed = 3
http_invalid_timeout = 4
OTHERS = 5 ).
IF sy-subrc <> 0.
ls_bapiret2-id = sy-msgid.ls_bapiret2-number = sy-msgno.ls_bapiret2-type = sy-msgty.
ls_bapiret2-message_v1 = sy-msgv1.ls_bapiret2-message_v2 = sy-msgv2.ls_bapiret2-message_v3 = sy-msgv3.ls_bapiret2-message_v4 = sy-msgv4.
APPEND ls_bapiret2 TO return_tab.
RETURN.
ENDIF.
lr_http_client->receive(
EXCEPTIONS
http_communication_failure = 1
http_invalid_state = 2
http_processing_failed = 3
OTHERS = 5 ).
IF sy-subrc <> 0.
ls_bapiret2-id = sy-msgid.ls_bapiret2-number = sy-msgno.ls_bapiret2-type = sy-msgty.
ls_bapiret2-message_v1 = sy-msgv1.ls_bapiret2-message_v2 = sy-msgv2.ls_bapiret2-message_v3 = sy-msgv3.ls_bapiret2-message_v4 = sy-msgv4.
APPEND ls_bapiret2 TO return_tab.
RETURN.
ENDIF.
lr_http_client->response->get_status( IMPORTING code = lv_status_code reason = lv_status_reason ).
" close the connection
lr_http_client->close(
EXCEPTIONS
http_invalid_state = 1 " Invalid state
OTHERS = 2
).
IF sy-subrc <> 0.
ls_bapiret2-id = sy-msgid.ls_bapiret2-number = sy-msgno.ls_bapiret2-type = sy-msgty.
ls_bapiret2-message_v1 = sy-msgv1.ls_bapiret2-message_v2 = sy-msgv2.ls_bapiret2-message_v3 = sy-msgv3.ls_bapiret2-message_v4 = sy-msgv4.
APPEND ls_bapiret2 TO return_tab.
RETURN.
ENDIF.
IF lv_status_code = 200 .
ls_bapiret2-id = 'ZAIF_AEM_MESSAGES'.
ls_bapiret2-number = '004'.
ls_bapiret2-type = 'S'.
ls_bapiret2-message_v1 = lv_status_code.
CONDENSE ls_bapiret2-message_v1.
APPEND ls_bapiret2 TO return_tab.
ELSE.
ls_bapiret2-id = 'ZAIF_AEM_MESSAGES'.
ls_bapiret2-number = '003'.
ls_bapiret2-type = 'E'.
ls_bapiret2-message_v1 = lv_status_code.
ls_bapiret2-message_v2 = lv_status_reason.
CONDENSE ls_bapiret2-message_v1.
APPEND ls_bapiret2 TO return_tab.
ENDIF.
ENDFUNCTION.
Step 4: Serialization
Based on the object key, I also defined a serialization. I did this is tansaction /AIF/CUST -> Interface Development -> Additional Interface Properties -> Define Serialization Settings
Serialization Object
I created a new serialization object of type “Internal Timestamp”.
As basis, I used the key field “OBJKEY”.
Step 5: Error Handling
To improve error handling, I added key fields and an interface display name in /AIF/CUST -> Error Handling -> Define Namespace-Specific Features and -> Define Interface-Specific Feature.
Key Field
I defined all fields of the raw structure as key fields, but only the object key is visible. Index table was creates accordingly.
Interface Display Name
As interface display name, I defined the event type:
Step 6: Interface Determination
As the raw structure can be reused for multiple BOR events, I created an interface determination in /AIF/CUST -> System Configuration -> Interface Determination -> Define Interface Determination for XML Interfaces based on the object key and event.
The following are the specific settings:
Step 7 – Name Mapping Table
For converting the DDIC names to JSon names I created the table: ZAIF_AEM_NAME_MA
For my specific use case I filled with following entries:
Content:
NAME_MAPPING_ID ABAP JSON
BPCHANGE | DATA | data |
BPCHANGE | DATACONTENTTYPE | datacontenttype |
BPCHANGE | ID | id |
BPCHANGE | NAME_FIRST | FirstName |
BPCHANGE | NAME_LAST | LastName |
BPCHANGE | PARTNER | BusinessPartner |
BPCHANGE | SPECVERSION | specversion |
BPCHANGE | TIME | time |
BPCHANGE | TYPE | type |
BPCHANGE | COUNTRY | country |
Monitoring
The messages that were created can be seen in transaction Monitoring and Error Handling (/AIF/ERR).
They can also be seen in the SAP Fiori Message Monitoring App: