Skip to Content
Technical Articles
Author's profile photo Marcel Wahl

Exposing BAPI as OData API using RAP Facade

🔥 Updates

Thank you colleagues for the feedback. As per our discussion we could simplify this guide even more by using a managed RAP implementation with an “unmanaged save”. The solution does now no  longer require a custom workspace buffer but is using the RAP default implementation.


During the last months we often received the question, how to wrap SAP S/4HANA BAPIs for use in side-by-side scenarios, for example, from SAP Cloud Application Programming Model (CAP).

There are multiple ways to achieve that on a purely technical level basically enabling the module to be called over HTTPS. But the overwhelming interface only lead to meeting after meeting clarifying the purpose of fields of which 90% are not needed for the business use case.

To avoid that we came up with the approach to model facades in ABAP RESTful Programming Model (RAP) that reduce the surface of the API to the minimum and split by the need-to-know principle. The functional experts in the SAP S/4HANA business system could easily tell what input is expect from the end-user / consumer. Anything else was hidden in the RAP facade.

This is an end-to-end “how-to” guide with code snippets focusing on the main features needed to achieve this.

  1. Modelling + implementing RAP
  2. Calling the BAPI
  3. Error handling
  4. Testing in ABAP + POSTMAN
  5. Remarks on transaction handling


This blog is based on “Using BAPIs in RAP” and was written with support of Marcel Hermanns and Renzo Colle. Thanks a lot for for the insights.

Sample Use Case

This is a real use case of an incentive / bonus payment for managers used in the performance review cycles.

The managers only want to specify the amount and employee it is for.

The finance experts in the backend know that they have to create a finance posting with debit on corporate benefits money pool and credit to an employee payout account.

Solution Overview


Solution Overview

Entity Model

The bonus payment is modelled as simple as possible on top of the SAP standard accounting document.

The item structure of an accounting document is hidden by using the a unique main item projection.


@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'RAP Facade: Bonus Payment main item'
@Metadata.ignorePropagatedAnnotations: true
define view entity ZDemo_BonusPaymentItem
  as select from I_OperationalAcctgDocItem
  association [1] to I_OperationalAcctgDocItem as _AcctDocItem on  _AcctDocItem.CompanyCode            = $projection.CompanyCode
                                                               and _AcctDocItem.FiscalYear             = $projection.FiscalYear
                                                               and _AcctDocItem.AccountingDocument     = $projection.AccountingDocument
                                                               and _AcctDocItem.AccountingDocumentItem = $projection.MainItem
  key CompanyCode, //Access control
  key FiscalYear,
  key AccountingDocument,
      min( AccountingDocumentItem ) as MainItem,
// Assumption: 
// every bonus payment has exactly 1 debit item with the total bonus amount
// there may be multiple credit items
  DebitCreditCode = 'S' //Debit line
group by


The main entity for the WebAPI is a simplified projection on the accounting document mixed with the figures of the main item.


@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'RAP Facade: Bonus Payment'
define root view entity ZDemo_BonusPayment
  as select from I_AccountingDocument
  //Main item
  association [0..1] to ZDemo_BonusPaymentItem as _MainItem on  _MainItem.CompanyCode        = $projection.CompanyCode
                                                            and _MainItem.FiscalYear         = $projection.FiscalYear
                                                            and _MainItem.AccountingDocument = $projection.AccountingDocument
  key CompanyCode, // Access control
  key FiscalYear,
  key AccountingDocument,

      //main attributes

      // main item figures - currency relationship inherited
      _MainItem._AcctDocItem.CompanyCodeCurrency         as Currency,
      _MainItem._AcctDocItem.AmountInCompanyCodeCurrency as Amount,

      //Administrative data
      CreationTime                                       as CreatedAt,
      AccountingDocumentCreationDate                     as CreatedOn,
      AccountingDocCreatedByUser                         as CreatedBy,

      //Document reference used for employee field
      DocumentReferenceID                                as BonusRecipient
      AccountingDocumentType =  'WA'
  and IsReversed             != 'X'


RAP Facade

Creating a behavior definition

To enable creation of new bonus payouts we are defining a behavior definition for this entity with minimal information.

We will use a “managed” implementation with “unmanaged save” to get the transaction workspace handling for free.

As with all BAPIs, the final key will only be available after the real posting run.
Hence “late numbering” must be activated.


managed implementation in class zbp_demo_bonuspayment unique;
strict ( 2 );

define behavior for ZDemo_BonusPayment alias BonusPayment
with unmanaged save
late numbering  // <<<< must have when working with the BAPI
lock master
authorization master ( instance ) // based on company code field
etag master CreatedAt
  field ( readonly ) AccountingDocument, FiscalYear; //Key fields filled by API
  field ( readonly : update ) CompanyCode; //Key fields provided from external
  field ( readonly ) CreatedAt, CreatedBy, CreatedOn; //Admin fields

  delete; //Cancellation - Not implemented yet



  1. We do not need draft capabilities because WebAPIs do synchronous calls and dont need it.
  2. We do not require a behavior definition for the item view since it serves only as a filter.
  3. Method “Delete” is included for demo purposes but not fully implemented

Overview of behavior runtime

A RAP facade for a BAPI requires only the minimal methods for writing in the saver class:

  1. Filling the BAPI call by merging requested data with hidden internal control fields
    e.g. document type
  2. Calling the BAPI and handling all errors
  3. Returning the final keys

Save sequence using a BAPI

The BAPI call is part of the save sequence going through the following methods:

  1. “Adjust_Numbers”
    • The BAPI must be called here, since later you can no longer handle errors or return the keys
  2. “Save_Modified”
    • This method is not required because the BAPI has already written the data. It can be left empty.


  1. To handle the BAPI errors, you must change the inheritance of your local saver class definition from “cl_abap_behavior_saver” to “cl_abap_behavior_saver_failed“.
    This will enable the additional parameter “failed” in method “adjust_numbers”.See RAP Saver Classes and Methods in ABAP Keyword Documentation for more details
  2. BAPIs often use “CALL FUNCTION … IN UPDATE TASK” which is forbidden during RAP runtime except in “adjust numbers” and the save methods.

Internal control fields and defaults

BAPIs require a set of control parameters such as document types, company code context and so on.

The RAP Facade requires access to these values which can be implemented in various ways:

  1. BRF+ function returning a structure with all fields
  2. Configuration class reading from a configuration table
  3. Member structure with hard coded defaults — FOR DEMO PURPOSES ONLY !

For the sake of a copy + paste demo, we defined our configuration in the saver class private section:


      "! <p class="shorttext synchronized" lang="en">Configuration</p>
      "! <p>hard coded just for demo purposes only </p>
      "! <p>Use a configuration table or BRF+ rule to fill
      "! the values in a real use case </p>
      BEGIN OF ms_config,
        company_code  TYPE bapiache09-comp_code VALUE '1010',
        document_type TYPE bapiache09-doc_type VALUE 'WA',
        BEGIN OF debit,
          transaction TYPE bapiacgl09-acct_key VALUE 'BSX',
          glaccount   TYPE bapiacgl09-gl_account VALUE '0054070000',
        END OF debit,
        BEGIN OF credit,
          transaction TYPE bapiacgl09-acct_key VALUE 'GBB',
          glaccount   TYPE bapiacgl09-gl_account VALUE '0013600000',
        END OF credit,
      END OF ms_config ##no_text.


Filling BAPI Requests

To keep the code as easy as possible, it is a good a practice to have all BAPI parameters in a request structure type and build a request table based on it.


      "! <p class="shorttext synchronized" lang="en">Accounting document request</p>
      BEGIN OF ty_s_bapi_request,
        pid      TYPE abp_behv_pid,
        header   TYPE bapiache09,
        amounts  TYPE STANDARD TABLE OF bapiaccr09 WITH DEFAULT KEY,
        accounts TYPE STANDARD TABLE OF bapiacgl09 WITH DEFAULT KEY,
      END OF ty_s_bapi_request.


The methods “fill_header” and “fill_amounts” follow the same pattern:

  1. Map fields from RAP structure to BAPI structure
  2. Fill control fields from configuration

Example for header:


  METHOD fill_header.
    CLEAR es_header.
    es_header-comp_code = is_bonus-CompanyCode.
    es_header-fisc_year = is_bonus-FiscalYear.
    es_header-doc_date = is_bonus-DocumentDate.
    es_header-pstng_date = is_bonus-PostingDate.
    "document references
    es_header-ref_doc_no = is_bonus-BonusRecipient.
    "determination of internal attributes
    det_header_defaults( EXPORTING is_bonus = is_bonus
                         CHANGING cs_header = es_header
                                  cs_reported = cs_reported ).


The example code for control field determination can be found in the appendix below.

Optional: determinations and validations before save

You can use RAP determinations and validations to do any custom logic before saving the data record, for example, filling field defaults that havent been provided by the caller.

Avoid coding any validation here that is anyway covered by ther BAPI logic.

BAPI call and final keys

For readable code during BAPI processing a result type and table can look like this:


    "there is no re-usable structure to split this key
      BEGIN OF ty_s_acc_doc_key,
        belnr TYPE belnr_d,
        bukrs TYPE bukrs,
        gjahr TYPE gjahr,
      END OF ty_s_acc_doc_key.
      "! <p class="shorttext synchronized" lang="en">Accounting document result</p>
      BEGIN OF ty_s_bapi_result,
        pid      TYPE abp_behv_pid,
        temp_key TYPE ty_s_acc_doc_key,
        key      TYPE awkey,
        msg      TYPE STANDARD TABLE OF bapiret2 WITH DEFAULT KEY,
      END OF ty_s_bapi_result.


BAPI main processing is then implemented in method “adjust_numbers“:


METHOD adjust_numbers.

    DATA lt_bapi_res TYPE SORTED TABLE OF ty_s_bapi_result WITH UNIQUE KEY pid.
    DATA lt_bapi_req TYPE SORTED TABLE OF ty_s_bapi_request WITH UNIQUE KEY pid.

    "get all records from buffer
    READ ENTITY IN LOCAL MODE ZDemo_BonusPayment\\BonusPayment
     ALL FIELDS WITH VALUE #( FOR ls_Payment IN mapped-bonuspayment ( %pky = ls_payment-%pre ) )
     RESULT DATA(lt_payments).

    "prepare the create requests
    LOOP AT mapped-bonuspayment ASSIGNING FIELD-SYMBOL(<ls_bonus_key>).
          "get the record from transaction buffer for this key
          DATA(lr_bonus) = REF #( lt_payments[ KEY pid COMPONENTS %pid = <ls_bonus_key>-%pid
                                                                  %key = <ls_bonus_key>-%tmp ] ).
          "new BAPI request
          DATA(ls_bapi_request) = VALUE ty_s_bapi_request( pid = lr_bonus->%pid ).

          "fill header
            EXPORTING is_bonus = lr_bonus->*
            IMPORTING es_header = ls_bapi_request-header ).

          "fill amounts and account information
          "Every payment is a combination of a debit and credit item
             EXPORTING is_bonus_payment = lr_bonus->%data
                       is_header = ls_bapi_request-header
             CHANGING  ct_amounts = ls_bapi_request-currency_amount
                       ct_accounts = ls_bapi_request-accountgl ).

          "call the BAPI
          DATA(ls_bapi_result) = VALUE ty_s_bapi_result( pid = ls_bapi_request-pid ).
              documentheader = ls_bapi_request-header
              obj_key        = ls_bapi_result-acct_doc_key
              accountgl      = ls_bapi_request-accountgl
              currencyamount = ls_bapi_request-currency_amount
              return         = ls_bapi_result-msg.

          "handle errors
          handle_bapi_result(  EXPORTING is_result = ls_bapi_result
                               CHANGING cs_reported = reported ).

          "split key string
          DATA(ls_acc_doc_key) = CONV ty_s_acc_doc_key( ls_bapi_result-acct_doc_key ).
          <ls_bonus_key>-companycode = ls_acc_doc_key-bukrs.
          <ls_bonus_key>-fiscalyear = ls_acc_doc_key-gjahr.
          <ls_bonus_key>-accountingdocument = ls_acc_doc_key-belnr.

        CATCH zcx_demo_rap_facade_web_api INTO DATA(lx_bapi_error).
          "log error reason
          APPEND VALUE #( %pky = <ls_bonus_key>-%pre
                          %msg = lx_bapi_error ) TO reported-bonuspayment.
          "BAPI call or preparation failed
          INSERT VALUE #( %pky = <ls_bonus_key>-%pre
                          %create = if_abap_behv=>mk-on
          ) INTO TABLE failed-bonuspayment.



OData V4 Web API

The root entity view is exposed as OData V4 service using a new service definition:


@EndUserText.label: 'RAP facade demo: bonus payment'
define service ZDEMO_BONUS_PAYMENT {
  expose ZDemo_BonusPayment as BonusPayments;


Service Binding is created using template OData V4 – WebAPI:



Service Binding


Testing with Postman

For the real test you can setup a Postman collection with the following requests:

  1. GET – Read the entity set
    • URL:  https://<server>/sap/opu/odata4/sap/zdemo_api_bonus_payment_o4/srvd_a2x/sap/zdemo_bonus_payment/0001/BonusPayments
    • Header parameter to fetch CSRF Token
      “X-CSRF-Token = Fetch”
  2. POST – to Create a new payment
    • URL:  same as for above
    • Header parameter to set the CSRF Token from read
      “x-csrf-token = ….”
    • Payload raw, please choose type JSON
          "Currency": "EUR",
          "Amount": 100.00,
          "BonusRecipient": "Jan Sample"
  3. Sample response JSON:
    • The response should look like this
          "@odata.context": "$metadata#BonusPayments/$entity",
          "@odata.metadataEtag": "W/\"20230731082241\"",
          "@odata.etag": "W/\"SADL-303832333039~082309\"",
          "CompanyCode": "1010",
          "FiscalYear": "2023",
          "AccountingDocument": "4900010896",
          "DocumentDate": "2023-07-31",
          "PostingDate": "2023-07-31",
          "Currency": "EUR",
          "Amount": 100,
          "CreatedAt": "08:23:09",
          "CreatedOn": "2023-07-31",
          "CreatedBy": "HIDDEN",
          "BonusRecipient": "Jan Sample",
          "SAP__Messages": []


ABAP test runner

For a quick test, create an executable class with interface “if_oo_adt_classrun”.


"! <p class="shorttext synchronized" lang="en">RAP Facade: Bonus payment test run</p>
    INTERFACES if_oo_adt_classrun .


The main method can run the end-to-end test with minimal data:


  METHOD if_oo_adt_classrun~main.

    TYPES ty_s_bonus_data TYPE STRUCTURE FOR CREATE zdemo_bonuspayment\\BonusPayment.

    "fill minimal data
    DATA(ls_bonus) = VALUE ty_s_bonus_data(
      %cid = 'CREATE_MINIMAL_001'
      CompanyCode = '1010'
      PostingDate = sy-datum "optional
      amount = 500
      Currency = 'EUR'
      %control = VALUE #(
          CompanyCode = if_abap_behv=>mk-on
          PostingDate = if_abap_behv=>mk-on
          amount = if_abap_behv=>mk-on
          Currency = if_abap_behv=>mk-on
    ) ##no_text.

    MODIFY ENTITY zdemo_bonuspayment\\BonusPayment
     CREATE FROM VALUE #( ( ls_bonus ) )
     MAPPED DATA(ls_create_mapped)
     FAILED DATA(ls_create_failed)
     REPORTED DATA(ls_create_reported).

    "check for problems
    IF ls_create_failed IS NOT INITIAL.
      LOOP AT ls_create_reported-bonuspayment ASSIGNING FIELD-SYMBOL(<ls_create_msg>).
        out->write( <ls_create_msg>-%msg->if_message~get_text( ) ).
      out->write( 'Creation failed'(e01) ).

     RESPONSE OF zdemo_bonuspayment
      FAILED DATA(ls_save_failed)
      REPORTED DATA(ls_save_reported).

    "check for problems
    IF ls_save_failed IS NOT INITIAL.
      LOOP AT ls_save_reported-bonuspayment ASSIGNING FIELD-SYMBOL(<ls_save_msg>).
        out->write( <ls_save_msg>-%msg->if_message~get_text( ) ).
      out->write( 'Saving failed'(e01) ).

    LOOP AT ls_create_mapped-bonuspayment ASSIGNING FIELD-SYMBOL(<ls_temp_key>).
      "get the final keys from late numbering
      CONVERT KEY OF zdemo_bonuspayment\\BonusPayment
       FROM TEMPORARY VALUE #( %pid = <ls_temp_key>-%pid
                               %tmp = <ls_temp_key>-%key ) TO DATA(ls_acc_doc_key).
      "check we have a new document key
      IF ls_acc_doc_key-AccountingDocument IS INITIAL.
        out->write( 'Saving did not generate AccountingDocument key'(e02) ).
        out->write( |Generated Accounting Document key { ls_acc_doc_key-AccountingDocument }| ).


    "check for problems
    IF ls_save_failed IS NOT INITIAL.
      out->write( 'Saving failed'(e03) ).

    "re-read new record from DB
    READ ENTITY zdemo_bonuspayment\\BonusPayment
     FROM VALUE #( ( CORRESPONDING #( ls_acc_doc_key ) ) )
     RESULT DATA(lt_new_payment_data).

    "check for problems
    IF lt_new_payment_data IS INITIAL.
      out->write( 'Re-read accounting document failed'(e04) ).



Transaction handling

Database commits

Use of statements causing database commits like “Commit”, “Call function destination” or “wait” must not be used inside the RAP processing. It can lead to data inconsistencies and will be prevented by RAP runtime checks in most cases.

Mass processing

When enabling “draft”, the draft persistency can be used as staging tables for mass processing.
This approach has been successfully used for initial load of millions of records in multiple projects and scales pretty well, for example, if used together with SAP Application Interface Framework (AIF).

Use of “Call function destination ‘NONE’”

It is common practice to use “Call Function Destination ‘NONE’” in mass processing and AIF interfaces for better control in error situations and memory management.

This concept must not be used inside RAP saver implementations but only as part of the load balancing outside the RAP entity processing.



The following are support methods and objects just for you information.

Access control inheriting from accounting document


@EndUserText.label: 'Demo payment: read access control'
@MappingRole: true
//Inheriting read access from accouting document by company code
    grant select on ZDemo_BonusPayment
    where inheriting conditions from entity I_AccountingDocument;                       

    grant select on ZDemo_BonusPaymentItem
    where inheriting conditions from entity I_AccountingDocument;                        


RAP compatible exception class

For debugging it is very useful to have exception classes instead of messages since they contain the triggering source code position.

When you enable your exception class as RAP message class they will be forwarded  throughout the stack and you jump to the trigger point even from many levels above.

Note: Make sure to default the message severity in the “Constructor”.

"! <p class="shorttext synchronized" lang="en">RAP demo: error messages</p>
CLASS zcx_demo_rap_facade_web_api DEFINITION
  INHERITING FROM cx_static_check

    INTERFACES if_abap_behv_message .
      "! <p class="shorttext synchronized" lang="en">Unexpected technical error</p>
      BEGIN OF c_tech_error,
        msgid TYPE symsgid VALUE 'ZDEMO_RAP_FACADE_WEB',
        msgno TYPE symsgno VALUE '002',
        attr1 TYPE scx_attrname VALUE '',
        attr2 TYPE scx_attrname VALUE '',
        attr3 TYPE scx_attrname VALUE '',
        attr4 TYPE scx_attrname VALUE '',
      END OF c_tech_error ##no_text.
      "! <p class="shorttext synchronized" lang="en">Document already exists</p>
      BEGIN OF c_already_exists,
        msgid TYPE symsgid VALUE 'ZDEMO_RAP_FACADE_WEB',
        msgno TYPE symsgno VALUE '001',
        attr1 TYPE scx_attrname VALUE 'MV_ACCOUNTING_DOCUMENT',
        attr2 TYPE scx_attrname VALUE 'MV_COMPANY_CODE',
        attr3 TYPE scx_attrname VALUE 'MV_FISCAL_YEAR',
        attr4 TYPE scx_attrname VALUE '',
      END OF c_already_exists ##no_text.

    "! <p class="shorttext synchronized" lang="en">Company code</p>
    DATA mv_company_code TYPE ZDemo_BonusPayment-CompanyCode.
    "! <p class="shorttext synchronized" lang="en">Fiscal year</p>
    DATA mv_Fiscal_Year TYPE ZDemo_BonusPayment-FiscalYear.
    "! <p class="shorttext synchronized" lang="en">Document number</p>
    DATA mv_Accounting_Document TYPE ZDemo_BonusPayment-AccountingDocument.

    "! <p class="shorttext synchronized" lang="en">CONSTRUCTOR</p>
    METHODS constructor
        textid                 LIKE if_t100_message=>t100key OPTIONAL
        previous               LIKE previous OPTIONAL
        severity               TYPE if_abap_behv_message=>t_severity DEFAULT if_abap_behv_message=>severity-error
        mv_company_code        TYPE zdemo_bonuspayment-companycode OPTIONAL
        mv_fiscal_year         TYPE zdemo_bonuspayment-fiscalyear OPTIONAL
        mv_accounting_document TYPE zdemo_bonuspayment-accountingdocument OPTIONAL.

CLASS zcx_demo_rap_facade_web_api IMPLEMENTATION.
    super->constructor( previous = previous ).

    me->mv_company_code = mv_company_code.
    me->mv_fiscal_year = mv_fiscal_year.
    me->mv_accounting_document = mv_accounting_document.

    "RAP message severity
    me->if_abap_behv_message~m_severity = severity. //<<<<< manually added!

    CLEAR me->textid.
    IF textid IS INITIAL.
      if_t100_message~t100key = if_t100_message=>default_textid.
      if_t100_message~t100key = textid.


Filling document header from configuration


METHOD det_header_defaults.
    "values from configuration - hard coded just for demo purposes!
    IF cs_header-comp_code IS INITIAL.
      cs_header-comp_code = ms_config-company_code.

    cs_header-doc_type = ms_config-document_type.
    cs_header-header_txt = |Bonus payout ARE { cs_header-fisc_year } for { cs_header-ref_doc_no }| ##no_text. "demo only
    cs_header-username = sy-uname.

    IF cs_header-doc_date IS INITIAL. "fallback
      cs_header-doc_date = sy-datum.

    IF cs_header-pstng_date IS INITIAL. "fallback
      cs_header-pstng_date = sy-datum.

    "determine fiscal year if not provided
    IF cs_header-fisc_year IS INITIAL.
      DATA(ls_msg) = VALUE bapiret1( ).
          companycodeid = cs_header-comp_code
          posting_date  = cs_header-pstng_date
          fiscal_year   = cs_header-fisc_year
          return        = ls_msg.
      "handle errors
      IF ls_msg-type CA c_msg_type_error.
        RAISE EXCEPTION new_msg_from_bapi( CORRESPONDING #( ls_msg ) ).


Handling BAPI messages in RAP



    "! <p class="shorttext synchronized" lang="en">Late messages</p>
    TYPES ty_s_reported TYPE RESPONSE FOR REPORTED LATE zdemo_bonuspayment.
    "! <p class="shorttext synchronized" lang="en">Late errors</p>
    TYPES ty_s_failed TYPE RESPONSE FOR FAILED LATE zdemo_bonuspayment.

    "! <p class="shorttext synchronized" lang="en">Handle BAPI exceptions + messages</p>
    "! @parameter is_result | BAPI result
    METHODS handle_bapi_result
        is_result   TYPE bapiret2_tab
        cs_reported TYPE ty_s_reported
        cs_failed   TYPE ty_s_failed.




  METHOD handle_bapi_result.
    "process all messages
    LOOP AT is_result-msg ASSIGNING FIELD-SYMBOL(<ls_bapi_msg>).
      "Convert all messages
      DATA(lo_behv_msg) = new_msg_from_bapi( <ls_bapi_msg> ).
      "Stop on error
      IF <ls_bapi_msg>-type CA c_msg_type_error.
        DATA(lx_error_msg) = lo_behv_msg.
        APPEND VALUE #( %pid = is_result-pid
                        %msg = lo_behv_msg ) TO cs_reported-bonuspayment.
    "check a key was generated
    IF lx_error_msg IS NOT BOUND
    AND is_result-acct_doc_key IS INITIAL.
      "Unexpected technical error
      RAISE EXCEPTION TYPE zcx_demo_rap_facade_web_api
          textid = zcx_demo_rap_facade_web_api=>c_tech_error.
    "If there was an error, raise it
    IF lx_error_msg IS BOUND.
      RAISE EXCEPTION lx_error_msg.


Conversion of BAPI messages to RAP messages




    "! <p class="shorttext synchronized" lang="en">New RAP message from bapiret</p>
    "! @parameter is_message | BAPIRET message
    "! @parameter ro_msg | RAP behavior message
    METHODS new_msg_from_bapi
      IMPORTING is_message    TYPE bapiret2
      RETURNING VALUE(ro_msg) TYPE REF TO zcx_demo_rap_facade_web_api.




  METHOD new_msg_from_bapi.
    CHECK is_message IS NOT INITIAL.
    "determine severity
    DATA(lv_msg_type) = COND #( WHEN is_message-type IS NOT INITIAL THEN is_message-type
                                ELSE if_abap_behv_message=>severity-error ).
    "set system variables
    MESSAGE ID is_message-id TYPE lv_msg_type NUMBER is_message-number
       WITH is_message-message_v1 is_message-message_v2 is_message-message_v3 is_message-message_v4
       INTO DATA(lv_msg).
        "raise message from system variables
        RAISE EXCEPTION TYPE zcx_demo_rap_facade_web_api USING MESSAGE.
      CATCH zcx_demo_rap_facade_web_api INTO ro_msg ##no_handler.


The concepts for wrapping APIs in SAP S/4HANA is based on the official guidelines:

ABAP Cloud API Enablement Guidelines for SAP S/4HANA Cloud, private edition, and SAP S/4HANA

Extend SAP S/4HANA in the cloud and on premise with ABAP based extensions

Assigned Tags

      You must be Logged on to comment or reply to a post.
      Author's profile photo Scott Lawton
      Scott Lawton

      Thanks for this blog, Marcel Wahl, it's very informative. I do have a question, though. Using a RAP facade to expose a BAPI as an OData web API seems like significantly more work than using the ABAP REST Library to wrap the BAPI and expose it as a "basic" (i.e. not OData) REST service. What are the advantages of using a RAP facade? (For that matter, the RAP facade approach also seems more complicated than using the SAP Gateway, which in turn seems more complicated than the REST Library.)

      Also, it seems like the RAP facade method to some degree defeats the purpose of using a BAPI in the first place. One of the benefits of calling a BAPI is that the BAPI knows all of the underlying tables that need to be updated, thereby ensuring that the developer doesn't miss something important. The RAP facade appears to require creating a CDS view to expose certain fields from the underlying table(s) to map to the BAPI input. This is slightly easier in your example because there is already a well-defined CDS view that maps to the BAPI and you just need a projection on top of it to expose certain fields, but I work in FSCD and there are precious few standard CDS views delivered. If such detailed knowledge of the underlying tables is necessary, why even use the BAPI at all? Why not just create a full RAP application that directly does the updating of the tables? Or why use a RAP facade when either the ABAP REST Library or creating the OData service through the SAP Gateway seem to be less complex?

      Or is there something I'm missing that would the RAP facade less complicated than it appears?

      Thanks again for your blog. Any insights you can provide related to the reasons/benefits for the RAP facade approach would be greatly appreciated!



      Author's profile photo Marcel Wahl
      Marcel Wahl
      Blog Post Author

      Hi Scott,

      On your question regarding the REST Library, that is not related to RAP but purely about the quality level of your web service. OData has a lot of advantages compared to plain REST, most importantly the query syntax and the standardized metadata, which allows a very convenient integration into your consumer applications on BTP , any middleware or testing tool. ( without writing an yaml )

      RAP programming also is the recommended + future proof way going forward for any API in SAP.
      It brings clear structure to your code, handles buffering, locking and enforces a sophisticated end-2-end flow.

      The RAP part of this solution implements only the write / create part, while the CDS is the most convenient way of implementing a powerful query without a single line of SQL code.

      You get none of query features nor the RAP based API + metadata features with a plain REST service, and you will have to implement a ton of coding just to get it working.
      In the RAP facade, it is exactly one method for the business logic that does exactly what you want.

      On the last question: why using the BAPI at all? because it is an upgrade stable, released and well document API that is the best thing to use until there is an official new RAP based API delivered by SAP.

      Overall, you can always code things yourself on a lower level, but on the long run ( maintenance, upgrades, extensions, re-use ) you will not find a more efficient way than this.

      BR, Marcel


      Author's profile photo Alejandro Kinsbrunner
      Alejandro Kinsbrunner

      First of all, thank you for the post, it is very well-written, clear and serves as a perfect example for many real-life examples.

      Next, I'd like to add although you are not using the SAVE_MODIFIED, it is interesting to say that if we would be implementing the Delete(), it should be coded there.

      Regarading switching the inheritance of the local saver class for having access to the FAILED table, this has been the top thing I am taking from this wonderfull post!

      Does this mean that the no-go decision that was on the check_before_Save is being moved into theadjust_numbers method? My understanding (based on the tests I did) is Yes. Also, I am not seeing any further the need of raising the short dump if something fails upon adjust_numbers, do you agree?

      Keep it up with this great posts!