SAP S/4HANA, SAP Integration Suite, SAP Application Interface Framework

Integration of SAP S/4HANA with SAP Integration Suite, advanced event mesh (AEM) using AIF

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

BUS1006DEaif.businesspartner.change.de
BUS1006ENaif.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

BPCHANGEDATAdata
BPCHANGEDATACONTENTTYPEdatacontenttype
BPCHANGEIDid
BPCHANGENAME_FIRSTFirstName
BPCHANGENAME_LASTLastName
BPCHANGEPARTNERBusinessPartner
BPCHANGESPECVERSIONspecversion
BPCHANGETIMEtime
BPCHANGETYPEtype
BPCHANGECOUNTRYcountry

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: