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.
Benefits:
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.
Regards,
Ryan Crosby