Flexible and Simple ABAP Proxies to expose BAPI interfaces
Background: You have a requirement to expose some type of BAPI for A2A integration and you don’t want to use RFC but are aiming to keep it as simple as possible.
Disclaimer: In a lot of organizations the integration expert and the ABAP developers may be segregated, but it is not the case where I am so my apologies if I end up over-simplifying this as a result. Also, the implementation for ‘ZCL_ABAP_PROXY_MAPPER’ is complete for my requirements but may not work fully should you choose to try it so pay close attention to the typekind logic for your situation. ** Obsolete as of NW 7.5 ** – SAP has provided MOVE-CORRESPONDING xx TO yy EXPANDING NESTED TABLES.
How it first started: Below I’ve included a fake example (for simplicity reasons) that illustrates a common occurrence for passing data to the output after executing some BAPI, but it started because I got tired of always writing these static code sections for custom implementations. I want to pass the data back and I end up writing a bunch of static code with LOOP, APPEND, etc. to fill the output. Sometimes, I can use MOVE-CORRESPONDING as highlighted with the comment at line 42 but only if I don’t have mismatches like lines 50-51 and that still doesn’t help avoid a mountain of static code for deeply nested structures.
Direct Assignment is Not Possible: Foiled by the darn CONTROLLER (and even then I would have to declare ALL fields of the BAPI table structure to even try that)-
RTTS to the Rescue: A custom Z class that uses RTTS to handle both inward/outward conversion dynamically.
1. Public Section
class ZCL_ABAP_PROXY_MAPPER definition public final create public . *"* public components of class ZCL_ABAP_PROXY_MAPPER *"* do not include other source files here!!! public section. class-data PROXY_TO_OTHER type ZCONV_MODE value 2. "#EC NOTEXT . class-data OTHER_TO_PROXY type ZCONV_MODE value 1. "#EC NOTEXT . class-methods MAP_STRUCTURE importing !DIRECTION type ZCONV_MODE !INPUT type ANY changing !OUTPUT type ANY . class-methods MAP_TABLE importing !DIRECTION type ZCONV_MODE !INPUT type STANDARD TABLE changing !OUTPUT type STANDARD TABLE
2. Private Section
*"* private components of class ZCL_ABAP_PROXY_MAPPER *"* do not include other source files here!!! private section. class-methods READ_COMPONENTS importing !INDEX type ZCONV_MODE !INPUT type ANY changing !OUTPUT type ANY
3. MAP_TABLE Method
METHOD MAP_TABLE. DATA: wa_to TYPE REF TO data. FIELD-SYMBOLS: <wa_from> TYPE ANY, <wa_to> TYPE ANY. * add each entry of table to output CREATE DATA wa_to LIKE LINE OF output. ASSIGN wa_to->* TO <wa_to>. LOOP AT input ASSIGNING <wa_from>. CLEAR <wa_to>. zcl_abap_proxy_mapper=>read_components( EXPORTING index = direction input = <wa_from> CHANGING output = <wa_to> ). APPEND <wa_to> TO output. ENDLOOP. ENDMETHOD.
4. MAP_STRUCTURE Method
METHOD MAP_STRUCTURE. * Read components of structure for mapping zcl_abap_proxy_mapper=>read_components( EXPORTING index = direction input = input CHANGING output = output ). ENDMETHOD.
5. READ_COMPONENTS Method
METHOD READ_COMPONENTS. DATA: type_ref TYPE REF TO cl_abap_typedescr, stru_ref_from TYPE REF TO cl_abap_structdescr, lt_comp_from TYPE abap_compdescr_tab, wa_comp_from TYPE abap_compdescr, stru_ref_to TYPE REF TO cl_abap_structdescr, lt_comp_to TYPE abap_compdescr_tab, wa_comp_to TYPE abap_compdescr. FIELD-SYMBOLS: <from> TYPE ANY, <to> TYPE ANY. * Get structure reference from RTTS stru_ref_from ?= cl_abap_typedescr=>describe_by_data( input ). lt_comp_from = stru_ref_from->components. stru_ref_to ?= cl_abap_typedescr=>describe_by_data( output ). lt_comp_to = stru_ref_to->components. SORT lt_comp_to BY name. type_ref ?= stru_ref_from. * Always skip controller as first value LOOP AT lt_comp_from INTO wa_comp_from FROM index. * Only perform match if names with same type kind exist in both from and to READ TABLE lt_comp_to INTO wa_comp_to WITH KEY name = wa_comp_from-name BINARY SEARCH. IF sy-subrc = 0 AND * Types must match exactly or both be structures ( ( wa_comp_to-type_kind = wa_comp_from-type_kind ) OR ( wa_comp_to-type_kind = type_ref->typekind_struct1 AND wa_comp_from-type_kind = type_ref->typekind_struct2 ) OR ( wa_comp_to-type_kind = type_ref->typekind_struct2 AND wa_comp_from-type_kind = type_ref->typekind_struct1 ) ). * Peform assignment and mapping ASSIGN COMPONENT wa_comp_from-name OF STRUCTURE input TO <from>. ASSIGN COMPONENT wa_comp_from-name OF STRUCTURE output TO <to>. IF <from> IS ASSIGNED AND <to> IS ASSIGNED. IF wa_comp_from-type_kind = type_ref->typekind_table. "Table type zcl_abap_proxy_mapper=>map_table( EXPORTING direction = index input = <from> CHANGING output = <to> ). ELSEIF wa_comp_from-type_kind = type_ref->typekind_struct1 OR "Either flat or nested structures wa_comp_from-type_kind = type_ref->typekind_struct2. zcl_abap_proxy_mapper=>map_structure( EXPORTING direction = index input = <from> CHANGING output = <to> ). ELSE. "Anything else - direct assignment <to> = <from>. ENDIF. ENDIF. ENDIF. ENDLOOP. ENDMETHOD.
The Result (with Earlier Sample): See the difference in the required ABAP effort.
1. Local Types
TYPES: BEGIN OF ty_request, customer_number TYPE kunnr, sales_organization TYPE vkorg, material TYPE matnr, document_date TYPE datum, document_date_to TYPE datum, transaction_group TYPE char01, purchase_order_number TYPE bstkd, END OF ty_request, BEGIN OF ty_response, return TYPE bapireturn, sales_orders TYPE sra_bapiorders_t, END OF ty_response.
2. Proxy Method
METHOD zii_sales_processing_in1~sales_order_dynamic_query. DATA: ls_request TYPE ty_request, ls_response TYPE ty_response. * Initialize CLEAR: ls_request, ls_response. * Convert external request format to internal request format dynamically zcl_abap_proxy_mapper=>map_structure( EXPORTING direction = zcl_abap_proxy_mapper=>proxy_to_other input = input-sales_order_query_dynamic_requ CHANGING output = ls_request ). * Conversion exit for customer CALL FUNCTION 'CONVERSION_EXIT_ALPHA_INPUT' EXPORTING input = ls_request-customer_number IMPORTING output = ls_request-customer_number. * Execute BAPI search CALL FUNCTION 'BAPI_SALESORDER_GETLIST' EXPORTING customer_number = ls_request-customer_number sales_organization = ls_request-sales_organization material = ls_request-material document_date = ls_request-document_date document_date_to = ls_request-document_date_to transaction_group = ls_request-transaction_group purchase_order_number = ls_request-purchase_order_number IMPORTING return = ls_response-return TABLES sales_orders = ls_response-sales_orders. * Convert internal response format to external response format dynamically zcl_abap_proxy_mapper=>map_structure( EXPORTING direction = zcl_abap_proxy_mapper=>other_to_proxy input = ls_response CHANGING output = output-sales_order_query_dynamic_resp ). ENDMETHOD
But Wait – What about Performance?: I tested this rigorously over the period of several days and across multiple projects for each time I had a chance to use it and I did not observe any noticeable performance differences, and in some cases even found that the dynamic examples ran faster than the static counterparts. However, that said, I would not recommend such an approach for deeply nested structures with many levels and structures that are many, many columns wide because of the recursive algorithm.
1. Simplify and shorten ABAP coding effort.
2. If I start by plugging in types for all the BAPI parameters internally then new fields/structures can be made available for input or output by changing the corresponding request/response data type and merely executing a proxy regeneration.
3. If I want to expose friendlier business object names for non-SAP counterparts then I can use the mapping runtime in PI to handle such cases so I can still use dynamic RTTS internally. I could also choose to abstract a more complicated piece of a BAPI interface such as the EXTENSIONIN table in ‘BAPI_SALESORDER_SIMULATE’ for example.
Hope some users might find this approach helpful in some way and I would always welcome feedback.