BOPF Business Object Builder

SAP BOPF API Teil 2

Nachdem wir uns im 1. Teil im Allgemeinen mit der SAP BOPF API und im speziellen mit der Implementierung der CRUDQ-Operationen Create, Read und Update beschäftigt haben, folgt nun der 2. Teil mit den restlichen Operationen Delete und Query. Weitere Themen bei der Implementierung gegen die SAP BOPF API sind Assoziationen, Actions (entspricht OData Function Imports) und Ausnahme- und Transaktionsbehandlung.

Das Coding aus dem 1. Teil hat bereits diese Funktionalitäten umfasst. Du kannst ihn hier herunterladen und dann einfach in Deinem System implementieren. Die Unit Test Klasse findest Du hier.

1. DELETE_HEADER

Die delete_header Methode ist einfach zu implementieren. Das geschieht wieder über die uns wohlbekannte Methode modify des Service Managers.

* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_RL_EPM_SO_DAO->DELETE_HEADER
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_KEY                         TYPE        /BOBF/CONF_KEY
* | [!CX!] CX_DEMO_DYN_T100
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD delete_header.
    DATA lr_change   TYPE REF TO /bobf/if_tra_change.
    DATA lr_message  TYPE REF TO /bobf/if_frw_message.
    DATA lt_mod      TYPE /bobf/t_frw_modification.
    FIELD-SYMBOLS   <ls_mod> TYPE /bobf/s_frw_modification.

    APPEND INITIAL LINE TO lt_mod ASSIGNING <ls_mod>.
    <ls_mod>-node        = /bobf/if_epm_sales_order_c=>sc_node-root.
    <ls_mod>-change_mode = /bobf/if_frw_c=>sc_modify_delete.
    <ls_mod>-key         = iv_key.

    mr_service_mgr->modify(
      EXPORTING
        it_modification = lt_mod
      IMPORTING
        eo_change       = lr_change
        eo_message      = lr_message ).

    raise_exc_by_bopf_msg( lr_message ).

  ENDMETHOD.

In der Modification Struktur sind der Knotentyp, der Change_mode sowie der Datenbankschlüssel zu füllen. Ein gute Hilfe, wie die Modification Struktur abhängig vom Szenario zu füllen ist, liefert übrigens die Dokumentation zu /bobf/s_frw_modification.

BOPF API Modification Struktur Doku

2. QUERY_HEADER

Die Query_header Methode erwartet für jedes selektierbare Feld eine Range-Tabelle. Auf Seiten der BOPF API benutzen wir die query Methode des Service Managers. Die query Methode erwartet im Parameter it_selection_parameters die Selektionsparameter als eine einzige Range-Tabelle.

Über den Parameter is_query_options können Parameter für das Paging (top, skip, maxRows) sowie Sortieren übergeben werden. Wir halten es hier einfach und benutzen nur maxRows.

Wichtig zu wissen ist, dass die query Methode direkt das Ergebnis des Datenbank SELECT zurückgibt. Es wird also nicht geprüft, ob ein zurückgegebener Datensatz in der aktuellen Transaktion bereits geändert wurde. Dieses Verhalten ist identisch zu persistenten ABAP Klassen und stellt somit keine Überraschung dar.

* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_RL_EPM_SO_DAO->QUERY_HEADER
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_MAX_ROW                     TYPE        I (default =0)
* | [--->] IT_RNG_SO_ID                   TYPE        SABP_T_RANGE_OPTIONS(optional)
* | [--->] IT_RNG_SO_STATUS               TYPE        SABP_T_RANGE_OPTIONS(optional)
* | [--->] IT_RNG_LCHG_DATE_TIME          TYPE        SABP_T_RANGE_OPTIONS(optional)
* | [<---] ET_HEADER                      TYPE        /BOBF/T_EPM_SO_ROOT
* | [!CX!] CX_DEMO_DYN_T100
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD QUERY_HEADER.
    DATA ls_query_options TYpe /bobf/s_frw_query_options.
    DATA lt_query_selparam TYPE /bobf/t_frw_query_selparam.
    field-SYMBOLS <ls_query_selparam> type /BOBF/S_FRW_QUERY_SELPARAM.
    DATA lr_message  TYPE REF TO /bobf/if_frw_message.

    ls_query_options-maximum_rows = iv_max_row.

*   Spezifischen Range SalesOrderId in die allgemeine Range Tabelle lt_query_selparam übernehmen
    LOOP AT it_rng_so_id ASSIGNING FIELD-SYMBOL(<ls_rng_so_id>).
      APPEND INITIAL LINE TO lt_query_selparam ASSIGNING <ls_query_selparam>.
      MOVE-CORRESPONDING <ls_rng_so_id> TO <ls_query_selparam>.
      <ls_query_selparam>-attribute_name = /bobf/if_epm_sales_order_c=>sc_node_attribute-root-so_id.
    ENDLOOP.

*   Spezifischen Range SalesOrder Status in die allgemeine Range Tabelle lt_query_selparam übernehmen
    LOOP AT it_rng_so_status ASSIGNING FIELD-SYMBOL(<ls_rng_so_status>).
      APPEND INITIAL LINE TO lt_query_selparam ASSIGNING <ls_query_selparam>.
      MOVE-CORRESPONDING <ls_rng_so_status> TO <ls_query_selparam>.
      <ls_query_selparam>-attribute_name = /bobf/if_epm_sales_order_c=>sc_node_attribute-root-so_status.
    ENDLOOP.

*   Spezifischen Range SalesOrder Change DateTime in die allgemeine Range Tabelle lt_query_selparam übernehmen
    LOOP AT it_rng_lchg_date_time ASSIGNING FIELD-SYMBOL(<ls_rng_lchg_date_time>).
      APPEND INITIAL LINE TO lt_query_selparam ASSIGNING <ls_query_selparam>.
      MOVE-CORRESPONDING <ls_rng_lchg_date_time> TO <ls_query_selparam>.
      <ls_query_selparam>-attribute_name = /bobf/if_epm_sales_order_c=>sc_node_attribute-root-lchg_date_time.
    ENDLOOP.

    mr_service_mgr->query(
      EXPORTING
        iv_query_key            = /bobf/if_epm_sales_order_c=>sc_query-root-select_by_elements
        it_selection_parameters = lt_query_selparam
        is_query_options        = ls_query_options
        iv_fill_data            = abap_true
      IMPORTING
        eo_message              = lr_message
        et_data                 = et_header ).

    raise_exc_by_bopf_msg( lr_message ).

  ENDMETHOD.

3. QUERY_ITEM_BY_HEADER_KEY

Eine Assoziation verknüpft BOPF Knotenelemente und wird datenbankseitig durch einen Fremdschlüssel abgebildet. Wir wollen hier die auf der Root-Knoten Salesorder Header definierte Assoziation ITEM benutzen, um ausgehend von einem Header Key die zugehörigen Salesorder Items zu  lesen.

BOPF API Assoziation

Die Methode query_item_by_header_key erhält den Schlüssel eines Salesorder Header und liest dann über die Assoziation Root->Item die Salesorder Items.  Typtechnisch werden die Items in einer interen Tabelle von Kombi-Strukturen zurückgegeben  (Tabellentyp /BOBF/T_EPM_SO_ITEM).

* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_RL_EPM_SO_DAO->QUERY_ITEM_BY_HEADER_KEY
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_HEADER_KEY                  TYPE        /BOBF/CONF_KEY
* | [<---] ET_ITEM                        TYPE        /BOBF/T_EPM_SO_ITEM
* | [!CX!] CX_DEMO_DYN_T100
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD query_item_by_header_key.
    DATA lr_message  TYPE REF TO /bobf/if_frw_message.

    mr_service_mgr->retrieve_by_association(
      EXPORTING
        iv_node_key             = /bobf/if_epm_sales_order_c=>sc_node-root "Source node
        it_key                  = VALUE #( ( key = iv_header_key ) )
        iv_association          = /bobf/if_epm_sales_order_c=>sc_association-root-item
        iv_fill_data            = abap_true
      IMPORTING
        eo_message              = lr_message
        et_data                 = et_item ).

    raise_exc_by_bopf_msg( lr_message ).

  ENDMETHOD.

4. Action CONFIRM_HEADER

Mit der Implementierung der CRUDQ-Operationen sind wir jetzt durch. Eine BOPF Action entspricht dem Funtion Import im OData Kontext. Wir wollen jetzt die Action CONFIRM definiert auf Header Ebene anbinden. Die Implementierung der Action in Klasse /BOBF/CL_EPM_SO_A_CHGE_STATUS ist schlicht: Das Datenbankfeld so_status wird ohne weitere Prüfung auf C gesetzt.

BOPF API Action

Die Action CONFIRM erwartet keine Import- und Exportparameter. Auf  Seite der BOPF API rufen wir deshalb auf dem Service Manager die Methode do_action ohne weitere Parameter nur mit dem Schlüssel der Salesorder auf.

* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_RL_EPM_SO_DAO->CONFIRM_HEADER
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_KEY                         TYPE        /BOBF/CONF_KEY
* | [!CX!] CX_DEMO_DYN_T100
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD confirm_header.
    DATA lr_change   TYPE REF TO /bobf/if_tra_change.
    DATA lr_message  TYPE REF TO /bobf/if_frw_message.

    mr_service_mgr->do_action(
      EXPORTING
        iv_act_key              = /bobf/if_epm_sales_order_c=>sc_action-root-confirm
        it_key                  = VALUE #( ( key = iv_key ) )
*        Action Confirm hat keine Import und Exportparameter
*        is_parameters           =
      IMPORTING
        eo_change               = lr_change
        eo_message              = lr_message ).

    raise_exc_by_bopf_msg( lr_message ).
  ENDMETHOD.

5. Ausnahmebehandlung

Die Methoden der BOPF API deklarieren keine Ausnahmen. Die Ausnahmebehandlung muss auf Seiten des Aufrufers durch Auswertung der Exportparameter eo_change und eo_message erfolgen. Über eo_change (Interface /BOBF/IF_TRA_CHANGE) lässt sich feststellen, ob es fehlgeschlagene Änderungen gab.

BOPF API Change Interface

Im Parameter eo_message wird ein (manchmal) Message Container mit Nachrichten zurückgegeben. Um nicht nach jedem BOPF API Aufruf die Ausnahmebehandlung erneut zu implementieren, habe ich die Methode RAISE_EXC_BY_BOPF_MSG implementiert. Der Aufrufer unserer DAO-Klasse soll im Fehlerfall eine klassenbasierte Ausnahme erhalten. Ich verwende hier die Demo Ausnahmeklasse CX_DEMO_DYN_T100, welche eine T100-Nachricht kapselt. In einem konkreten Anwendungsfall würde man eine Ausnahmeklasse analog zu CX_DEMO_DYN_T100 implementieren. Die Methode RAISE_EXC_BY_BOPF_MSG prüft, ob Fehlernachrichten vorliegen. Wenn ja, wird die 1. Fehlernachricht verwendet, um, die T100-Ausnahme zu erzeugen.

* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZCL_RL_EPM_SO_DAO->RAISE_EXC_BY_BOPF_MSG
* +-------------------------------------------------------------------------------------------------+
* | [--->] IR_MESSAGE                     TYPE REF TO /BOBF/IF_FRW_MESSAGE
* | [!CX!] CX_DEMO_DYN_T100
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD RAISE_EXC_BY_BOPF_MSG.
    DATA lt_message TYPE /bobf/t_frw_message_k.
    DATA lv_exc_msg(200) TYPE c.

    IF ir_message IS BOUND AND ir_message->check( ) EQ abap_true.
      ir_message->get_messages(
        EXPORTING
          iv_severity = /bobf/cm_frw=>co_severity_error
        IMPORTING
          et_message = lt_message ).

      lv_exc_msg = lt_message[ 1 ]-message->get_text( ).

*     &1&2&3&4
      MESSAGE e241(/BOBF/COM_GENERATOR) WITH lv_exc_msg+0(50) lv_exc_msg+50(50) lv_exc_msg+100(50) lv_exc_msg+150(50)
        INTO DATA(lv_sy_msg).

      raise_exc_by_sy( ).
    ENDIF.

  ENDMETHOD.

Für eine korrekte Ausnahmebehandlung reicht es nicht aus, nur auf die Parameter eo_change und eo_message zu schauen. Beispiele sind die read-Methoden READ_HEADER_BY_KEY und READ_HEADER_BY_SO_ID unserer DAO-Klasse, wo wir mittels der Service Manager Retrieve-Methode einen Datensatz lesen. Wird dieser nicht gefunden, wird keine Fehlernachricht gesetzt. Wir müssen deshalb hier prüfen, ob 1 Datensatz gefunden wurde.

    mr_service_mgr->retrieve(
      EXPORTING
        iv_node_key             = /bobf/if_epm_sales_order_c=>sc_node-root
        it_key                  = VALUE #( ( key = iv_key ) )
      IMPORTING
        eo_message              = lr_message
        et_data                 = lt_header ).

    raise_exc_by_bopf_msg( lr_message ).

    IF lines( lt_header ) = 0.
      raise_exc_by_msg( |Header { iv_key } nicht gefunden| ).
    ENDIF.

Für diesen Fall gibt es die Methode RAISE_EXC_BY_MSG. Sie erwartet einen String und wirft die T100-Ausnahme.

* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZCL_RL_EPM_SO_DAO->RAISE_EXC_BY_MSG
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_MSG                         TYPE        CLIKE
* | [!CX!] CX_DEMO_DYN_T100
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD raise_exc_by_msg.
    DATA lv_msg(200) TYPE c.

    lv_msg = iv_msg.

*   &1&2&3&4
    MESSAGE e241(/bobf/com_generator) WITH lv_msg+0(50) lv_msg+50(50) lv_msg+100(50) lv_msg+150(50)
      INTO DATA(lv_sy_msg).

    raise_exc_by_sy( ).

  ENDMETHOD.

Als 3. Möglichkeit eine T100-Ausnahme zu werfen, habe ich die Methode RAISE_EXC_BY_SY implementiert. Die Methode nimmt die T100-Nachricht aus den SY-Feldern. Diese Form von Ausnahmebehandlung brauchen wir, wenn wir Funktionsbausteine mit klassischer Ausnahmebehandlung aufrufen. In unserem Beispiel ist der Funktionsbaustein NUMBER_GET_NEXT, mit dem wir eine neue Salesorder Id ziehen.

* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZCL_RL_EPM_SO_DAO->RAISE_EXC_BY_SY
* +-------------------------------------------------------------------------------------------------+
* | [!CX!] CX_DEMO_DYN_T100
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD raise_exc_by_sy.

    RAISE EXCEPTION TYPE cx_demo_dyn_t100
      MESSAGE ID sy-msgid
      TYPE sy-msgty
      NUMBER sy-msgno
      WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.

  ENDMETHOD.

6. Transaktionsbehandlung

In der DAO-Klasse benutzen wir den Service Manager, um Datensätze zu lesen und zu schreiben. In der Praxis werde wir mehr als 1 DAO-Klasse haben, um z.B. die Zugriffe auf Items und Notes zu implementieren. Die Transaktionssteuerung sollte deshalb zentral außerhalb der DAO-Klassen erfolgen. In diesem Blog machen wir das in der setup-Methode der Unit-Test Klasse.

  METHOD setup.
    CREATE OBJECT mr_epm_so_dao.
    mr_transaction_mgr = /bobf/cl_tra_trans_mgr_factory=>get_transaction_manager( ).

*   Änderungen von der letzten Testmethode zurücksetzen. Ansonsten scheitern die
*   Testmethoden, weil diese auf der gleichen Salesorder rumändern
    mr_transaction_mgr->cleanup( ).
  ENDMETHOD.

Über /bobf/cl_tra_trans_mgr_factory=>get_transaction_manager( ) holen wir uns den den BOPF Transaction Manager. Das Speichern triggern wir in der Testmethode über die Methode save. Beim Commit work führt das BOPF Framework den generischen Verbucherbaustein /BOBF/CL_DAC_UPDATE aus, der pro zu ändernder Tabelle einmal aufgerufen wird. Der folgende Screenshot zeigt das Speichern im Debugger für die Unit Testmethode READ_UPDATE_HEADER. Standardmäßig verbucht der Transaction Manager synchron als local update task. Dies kann über den save-Parameter IV_TRANSACTION_PATTERN auf asynchrone Verbuchung umgestellt werden.

BOPF API Transaktion

7. SAP BOPF API Fazit

BOPF ist ein wichtiger Teil des ABAP Programming Model for SAP Fiori. BOPF steckt hinter den ObjectModel Annotations einer CDS View. Es gibt einer CDS View die Fähigkeit, nicht nur Daten zu lesen, sondern auch zu schreiben. Die in diesem Blog beschriebene BOPF API benötigt Du, wenn Du diese Geschäftsobjekte mittels Validations und Determinations erweiterst. Ein weiterer Anwendungsfall ist die Programmierung gegen die von SAP mit S/4HANA ausgelieferten Geschäftsobjekte.

Die  BOPF API ist ein bißchen tricky in ihrer Benutzung. Mit diesem Blog hoffe ich, dass ein bisschen klarer gemacht zu haben. Aufsetzend auf den von SAP ausgelieferten EPM Geschäftsobjekten lässt sich schnell das Know-How in der BOPF API als auch in BOPF im allgemeinen aufbauen.

 

Du programmierst, bist ABAP-interessiert und hast Lust coole Projekte mit uns zu machen? Wir suchen dich! Schau doch mal in unserer Stellenbeschreibung vorbei.

Über den Autor

Rüdiger Lühr

Kommentieren

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

You may use these HTML tags and attributes:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

+ 89 = 96